summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamian Johnson <atagar@torproject.org>2020-01-05 13:28:37 -0800
committerDamian Johnson <atagar@torproject.org>2020-01-05 13:28:37 -0800
commita396f57c3a55f34bb9f9d5976fc54fcc6561a8c4 (patch)
treef34f4715f5e7c5b2edf19558e0a683b984394d1b
parent4e21371e88662992fa21bc1f845c05c16ccee4f4 (diff)
parentbdd376bd9e25dcee54d43a47e10329d92f83cafd (diff)
Drop python 2.6 -> 3.5 support
As stated in our release announcement Stem 2.x will discontinue Python 2.x support now that the PSF has dropped it... https://blog.atagar.com/stem-release-1-8/ https://www.python.org/doc/sunset-python-2/ Presuming our next release is a year out, Python 3.6 is the oldest non-EOL version so picking it as our new minimum requirement... https://devguide.python.org/#status-of-python-branches No doubt this compatibility change will let us make far more simplifications, but for the moment simply addressing low hanging fruit.
-rwxr-xr-xcache_fallback_directories.py9
-rwxr-xr-xcache_manual.py9
-rw-r--r--docs/faq.rst5
-rwxr-xr-xrun_tests.py23
-rw-r--r--setup.py8
-rw-r--r--stem/__init__.py2
-rw-r--r--stem/client/__init__.py8
-rw-r--r--stem/client/cell.py4
-rw-r--r--stem/client/datatype.py65
-rw-r--r--stem/control.py27
-rw-r--r--stem/descriptor/__init__.py38
-rw-r--r--stem/descriptor/bandwidth_file.py11
-rw-r--r--stem/descriptor/collector.py13
-rw-r--r--stem/descriptor/export.py14
-rw-r--r--stem/descriptor/extrainfo_descriptor.py7
-rw-r--r--stem/descriptor/hidden_service.py8
-rw-r--r--stem/descriptor/microdescriptor.py8
-rw-r--r--stem/descriptor/reader.py64
-rw-r--r--stem/descriptor/remote.py11
-rw-r--r--stem/descriptor/router_status_entry.py4
-rw-r--r--stem/descriptor/server_descriptor.py13
-rw-r--r--stem/directory.py22
-rw-r--r--stem/exit_policy.py28
-rw-r--r--stem/interpreter/__init__.py13
-rw-r--r--stem/interpreter/autocomplete.py9
-rw-r--r--stem/interpreter/commands.py8
-rw-r--r--stem/interpreter/help.py9
-rw-r--r--stem/manual.py62
-rw-r--r--stem/prereq.py107
-rw-r--r--stem/process.py9
-rw-r--r--stem/response/__init__.py13
-rw-r--r--stem/response/events.py20
-rw-r--r--stem/response/getinfo.py3
-rw-r--r--stem/response/protocolinfo.py4
-rw-r--r--stem/socket.py32
-rw-r--r--stem/util/__init__.py57
-rw-r--r--stem/util/conf.py19
-rw-r--r--stem/util/connection.py13
-rw-r--r--stem/util/enum.py2
-rw-r--r--stem/util/log.py11
-rw-r--r--stem/util/lru_cache.py182
-rw-r--r--stem/util/ordereddict.py133
-rw-r--r--stem/util/proc.py12
-rw-r--r--stem/util/str_tools.py39
-rw-r--r--stem/util/system.py2
-rw-r--r--stem/util/test_tools.py59
-rw-r--r--stem/version.py8
-rw-r--r--test/integ/connection/authentication.py1
-rw-r--r--test/integ/connection/connect.py20
-rw-r--r--test/integ/control/base_controller.py2
-rw-r--r--test/integ/control/controller.py107
-rw-r--r--test/integ/descriptor/collector.py1
-rw-r--r--test/integ/descriptor/extrainfo_descriptor.py2
-rw-r--r--test/integ/descriptor/microdescriptor.py2
-rw-r--r--test/integ/descriptor/networkstatus.py8
-rw-r--r--test/integ/descriptor/server_descriptor.py2
-rw-r--r--test/integ/directory/fallback.py1
-rw-r--r--test/integ/installation.py8
-rw-r--r--test/integ/interpreter.py4
-rw-r--r--test/integ/manual.py26
-rw-r--r--test/integ/process.py49
-rw-r--r--test/integ/response/protocolinfo.py6
-rw-r--r--test/integ/util/connection.py11
-rw-r--r--test/integ/util/proc.py1
-rw-r--r--test/integ/util/system.py27
-rw-r--r--test/network.py35
-rw-r--r--test/task.py18
-rw-r--r--test/unit/client/size.py6
-rw-r--r--test/unit/connection/authentication.py8
-rw-r--r--test/unit/connection/connect.py19
-rw-r--r--test/unit/control/controller.py8
-rw-r--r--test/unit/descriptor/bandwidth_file.py21
-rw-r--r--test/unit/descriptor/collector.py34
-rw-r--r--test/unit/descriptor/compression.py1
-rw-r--r--test/unit/descriptor/export.py16
-rw-r--r--test/unit/descriptor/hidden_service_v3.py17
-rw-r--r--test/unit/descriptor/networkstatus/document_v3.py11
-rw-r--r--test/unit/descriptor/reader.py21
-rw-r--r--test/unit/descriptor/remote.py57
-rw-r--r--test/unit/descriptor/router_status_entry.py9
-rw-r--r--test/unit/descriptor/server_descriptor.py31
-rw-r--r--test/unit/directory/authority.py12
-rw-r--r--test/unit/directory/fallback.py29
-rw-r--r--test/unit/doctest.py8
-rw-r--r--test/unit/exit_policy/policy.py6
-rw-r--r--test/unit/installation.py5
-rw-r--r--test/unit/interpreter/__init__.py6
-rw-r--r--test/unit/interpreter/autocomplete.py9
-rw-r--r--test/unit/interpreter/commands.py8
-rw-r--r--test/unit/manual.py53
-rw-r--r--test/unit/response/events.py8
-rw-r--r--test/unit/response/protocolinfo.py8
-rw-r--r--test/unit/tutorial.py27
-rw-r--r--test/unit/tutorial_examples.py49
-rw-r--r--test/unit/util/connection.py30
-rw-r--r--test/unit/util/proc.py7
-rw-r--r--test/unit/util/system.py8
-rw-r--r--test/unit/version.py8
98 files changed, 475 insertions, 1623 deletions
diff --git a/cache_fallback_directories.py b/cache_fallback_directories.py
index cb413c07..91ad40c0 100755
--- a/cache_fallback_directories.py
+++ b/cache_fallback_directories.py
@@ -8,22 +8,17 @@ Caches tor's latest fallback directories.
import re
import sys
+import urllib.request
import stem.directory
import stem.util.system
-try:
- # account for urllib's change between python 2.x and 3.x
- import urllib.request as urllib
-except ImportError:
- import urllib2 as urllib
-
GITWEB_FALLBACK_LOG = 'https://gitweb.torproject.org/tor.git/log/src/app/config/fallback_dirs.inc'
FALLBACK_DIR_LINK = "href='/tor.git/commit/src/app/config/fallback_dirs.inc\\?id=([^']*)'"
if __name__ == '__main__':
try:
- fallback_dir_page = urllib.urlopen(GITWEB_FALLBACK_LOG).read()
+ fallback_dir_page = urllib.request.urlopen(GITWEB_FALLBACK_LOG).read()
fallback_dir_commit = re.search(FALLBACK_DIR_LINK, fallback_dir_page).group(1)
except:
print("Unable to determine the latest commit to edit tor's fallback directories: %s" % sys.exc_info()[1])
diff --git a/cache_manual.py b/cache_manual.py
index 7fe6ea70..5bc68b57 100755
--- a/cache_manual.py
+++ b/cache_manual.py
@@ -8,22 +8,17 @@ Caches tor's latest manual content. Run this to pick new man page changes.
import re
import sys
+import urllib.request
import stem.manual
import stem.util.system
-try:
- # account for urllib's change between python 2.x and 3.x
- import urllib.request as urllib
-except ImportError:
- import urllib2 as urllib
-
GITWEB_MAN_LOG = 'https://gitweb.torproject.org/tor.git/log/doc/tor.1.txt'
MAN_LOG_LINK = "href='/tor.git/commit/doc/tor.1.txt\\?id=([^']*)'"
if __name__ == '__main__':
try:
- man_log_page = urllib.urlopen(GITWEB_MAN_LOG).read()
+ man_log_page = urllib.request.urlopen(GITWEB_MAN_LOG).read()
man_commit = re.search(MAN_LOG_LINK, man_log_page).group(1)
except:
print("Unable to determine the latest commit to edit tor's man page: %s" % sys.exc_info()[1])
diff --git a/docs/faq.rst b/docs/faq.rst
index 63800b36..6bbc2f9a 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -86,7 +86,10 @@ Ubuntu you can install these with...
What Python versions is Stem compatible with?
---------------------------------------------
-Stem works with **Python 2.6 and greater**, including the Python 3.x series.
+Stem works with **Python 3.6 and greater**.
+
+If you require a deprecated Python version then please use Stem 1.8, which was
+compatible with **Python 2.6 and above**.
.. _can_i_interact_with_tors_controller_interface_directly:
diff --git a/run_tests.py b/run_tests.py
index 8d7ea45e..d6ced384 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -7,6 +7,7 @@ Runs unit and integration tests. For usage information run this with '--help'.
"""
import errno
+import io
import importlib
import logging
import multiprocessing
@@ -18,11 +19,6 @@ import time
import traceback
import unittest
-try:
- from StringIO import StringIO
-except ImportError:
- from io import StringIO
-
import stem.prereq
import stem.util.conf
import stem.util.log
@@ -194,20 +190,6 @@ def main():
println('Nothing to run (for usage provide --help)\n')
sys.exit()
- if not stem.prereq.is_mock_available():
- try:
- import mock
- println(MOCK_OUT_OF_DATE_MSG % mock.__version__)
- except ImportError:
- println(MOCK_UNAVAILABLE_MSG)
-
- if stem.util.system.is_available('pip'):
- println("You can get it by running 'sudo pip install mock'.")
- elif stem.util.system.is_available('apt-get'):
- println("You can get it by running 'sudo apt-get install python-mock'.")
-
- sys.exit(1)
-
test.task.run(
'INITIALISING',
test.task.STEM_VERSION,
@@ -215,7 +197,6 @@ def main():
test.task.PYTHON_VERSION,
test.task.PLATFORM_VERSION,
test.task.CRYPTO_VERSION,
- test.task.MOCK_VERSION,
test.task.PYFLAKES_VERSION,
test.task.PYCODESTYLE_VERSION,
test.task.CLEAN_PYC,
@@ -442,7 +423,7 @@ def _run_test(args, test_class, output_filters):
traceback.print_exc(exc)
return None
- test_results = StringIO()
+ test_results = io.StringIO()
run_result = stem.util.test_tools.TimedTestRunner(test_results, verbosity = 2).run(suite)
if args.verbose:
diff --git a/setup.py b/setup.py
index 8b6f1e4b..52b62dca 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@
#
# * Recache latest information (cache_manual.py and cache_fallback_directories.py)
#
-# * Test with python2.6, python2.7, python3, and pypy.
+# * Test with python3 and pypy.
# |- If using tox run...
# |
# | % tox -- --all --target RUN_ALL,ONLINE
@@ -92,11 +92,7 @@ To install you can either use...
pip install stem
-... or install from the source tarball. Stem supports both the python 2.x and 3.x series. To use its python3 counterpart you simply need to install using that version of python.
-
-::
-
- python3 setup.py install
+... or install from the source tarball. Stem supports Python 3.6 and above.
After that, give some `tutorials <https://stem.torproject.org/tutorials.html>`_ a try! For questions or to discuss project ideas we're available on `irc <https://www.torproject.org/about/contact.html.en#irc>`_ and the `tor-dev@ email list <https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-dev>`_.
""".strip()
diff --git a/stem/__init__.py b/stem/__init__.py
index 66bc24b5..3600e920 100644
--- a/stem/__init__.py
+++ b/stem/__init__.py
@@ -722,7 +722,7 @@ class SocketClosed(SocketError):
class DownloadFailed(IOError):
"""
Inability to download a resource. Python's urllib module raises
- a wide variety of undocumented exceptions (urllib2.URLError,
+ a wide variety of undocumented exceptions (urllib.request.URLError,
socket.timeout, and others).
This wraps lower level failures in a common exception type that
diff --git a/stem/client/__init__.py b/stem/client/__init__.py
index 307bd4e4..2abeac88 100644
--- a/stem/client/__init__.py
+++ b/stem/client/__init__.py
@@ -64,14 +64,6 @@ class Relay(object):
"""
def __init__(self, orport, link_protocol):
- # TODO: Python 3.x adds a getbuffer() method which
- # lets us get the size...
- #
- # https://stackoverflow.com/questions/26827055/python-how-to-get-iobytes-allocated-memory-length
- #
- # When we drop python 2.x support we should replace
- # self._orport_buffer with an io.BytesIO.
-
self.link_protocol = LinkProtocol(link_protocol)
self._orport = orport
self._orport_buffer = b'' # unread bytes
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 5de5e445..1db46a28 100644
--- a/stem/client/datatype.py
+++ b/stem/client/datatype.py
@@ -138,7 +138,6 @@ users.** See our :class:`~stem.client.Relay` the API you probably want.
import binascii
import collections
import hashlib
-import struct
import stem.client.cell
import stem.prereq
@@ -183,7 +182,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)
@@ -367,7 +366,7 @@ class Field(object):
class Size(Field):
"""
Unsigned `struct.pack format
- <https://docs.python.org/2/library/struct.html#format-characters>` for
+ <https://docs.python.org/3/library/struct.html#format-characters>` for
network-order fields.
==================== ===========
@@ -380,68 +379,30 @@ class Size(Field):
==================== ===========
"""
- def __init__(self, name, size, pack_format):
+ def __init__(self, name, size):
self.name = name
self.size = size
- self.format = pack_format
@staticmethod
def pop(packed):
raise NotImplementedError("Use our constant's unpack() and pop() instead")
def pack(self, content):
- # TODO: Python 2.6's struct module behaves a little differently in a couple
- # respsects...
- #
- # * Invalid types raise a TypeError rather than a struct.error.
- #
- # * Negative values are happily packed despite being unsigned fields with
- # a message printed to stdout (!) that says...
- #
- # stem/client/datatype.py:362: DeprecationWarning: struct integer overflow masking is deprecated
- # packed = struct.pack(self.format, content)
- # stem/client/datatype.py:362: DeprecationWarning: 'B' format requires 0 <= number <= 255
- # packed = struct.pack(self.format, content)
- #
- # Rather than adjust this method to account for these differences doing
- # duplicate upfront checks just for python 2.6. When we drop 2.6 support
- # this can obviously be dropped.
-
- if stem.prereq._is_python_26():
- if not stem.util._is_int(content):
- 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))
-
- # TODO: When we drop python 2.x support this can be simplified via
- # integer's to_bytes() method. For example...
- #
- # struct.pack('>Q', my_number)
- #
- # ... is the same as...
- #
- # my_number.to_bytes(8, 'big')
-
try:
- packed = struct.pack(self.format, content)
- except struct.error:
- if not stem.util._is_int(content):
+ return content.to_bytes(self.size, 'big')
+ except:
+ 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))
else:
- raise # some other struct exception
-
- if self.size != len(packed):
- raise ValueError('%s is the wrong size for a %s field' % (repr(packed), self.name))
-
- return packed
+ raise
def unpack(self, packed):
if self.size != len(packed):
raise ValueError('%s is the wrong size for a %s field' % (repr(packed), self.name))
- return struct.unpack(self.format, packed)[0]
+ return int.from_bytes(packed, 'big')
def pop(self, packed):
to_unpack, remainder = split(packed, self.size)
@@ -449,7 +410,7 @@ class Size(Field):
return self.unpack(to_unpack), remainder
def __hash__(self):
- return stem.util._hash_attr(self, 'name', 'size', 'format', cache = True)
+ return stem.util._hash_attr(self, 'name', 'size', cache = True)
class Address(Field):
@@ -745,7 +706,7 @@ def _unpack_ipv6_address(value):
return ':'.join(['%04x' % Size.SHORT.unpack(value[i * 2:(i + 1) * 2]) for i in range(8)])
-setattr(Size, 'CHAR', Size('CHAR', 1, '!B'))
-setattr(Size, 'SHORT', Size('SHORT', 2, '!H'))
-setattr(Size, 'LONG', Size('LONG', 4, '!L'))
-setattr(Size, 'LONG_LONG', Size('LONG_LONG', 8, '!Q'))
+setattr(Size, 'CHAR', Size('CHAR', 1))
+setattr(Size, 'SHORT', Size('SHORT', 2))
+setattr(Size, 'LONG', Size('LONG', 4))
+setattr(Size, 'LONG_LONG', Size('LONG_LONG', 8))
diff --git a/stem/control.py b/stem/control.py
index 56567467..4adec330 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -253,21 +253,10 @@ import functools
import inspect
import io
import os
+import queue
import threading
import time
-try:
- # Added in 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
-try:
- # Added in 3.x
- import queue
-except ImportError:
- import Queue as queue
-
import stem.descriptor.microdescriptor
import stem.descriptor.reader
import stem.descriptor.router_status_entry
@@ -1166,7 +1155,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:
@@ -1211,7 +1200,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)
@@ -2314,7 +2303,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
@@ -2623,7 +2612,7 @@ class Controller(BaseController):
log.debug('GETCONF HiddenServiceOptions (failed: %s)' % exc)
raise
- service_dir_map = OrderedDict()
+ service_dir_map = collections.OrderedDict()
directory = None
for status_code, divider, content in response.content():
@@ -2779,7 +2768,7 @@ class Controller(BaseController):
if path in conf and (port, target_address, target_port) in conf[path]['HiddenServicePort']:
return None
- conf.setdefault(path, OrderedDict()).setdefault('HiddenServicePort', []).append((port, target_address, target_port))
+ conf.setdefault(path, collections.OrderedDict()).setdefault('HiddenServicePort', []).append((port, target_address, target_port))
if auth_type and client_names:
hsac = "%s %s" % (auth_type, ','.join(client_names))
@@ -3482,7 +3471,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))
@@ -3637,7 +3626,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 c97db183..fff08910 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -106,12 +106,6 @@ import stem.util.enum
import stem.util.str_tools
import stem.util.system
-try:
- # added in python 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
__all__ = [
'bandwidth_file',
'certificate',
@@ -363,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:
@@ -377,14 +371,7 @@ def parse_file(descriptor_file, descriptor_type = None, validate = False, docume
return
- # Not all files are seekable. If unseekable then advising the user.
- #
- # Python 3.x adds an io.seekable() method, but not an option with python 2.x
- # so using an experimental call to tell() to determine this.
-
- try:
- descriptor_file.tell()
- except IOError:
+ if not descriptor_file.seekable():
raise IOError(UNSEEKABLE_MSG)
# The tor descriptor specifications do not provide a reliable method for
@@ -453,16 +440,10 @@ def _parse_file_for_path(descriptor_file, *args, **kwargs):
def _parse_file_for_tar_path(descriptor_file, *args, **kwargs):
- # TODO: use 'with' for tarfile after dropping python 2.6 support
- tar_file = tarfile.open(descriptor_file)
-
- try:
+ with tarfile.open(descriptor_file) as tar_file:
for desc in parse_file(tar_file, *args, **kwargs):
desc._set_path(os.path.abspath(descriptor_file))
yield desc
- finally:
- if tar_file:
- tar_file.close()
def _parse_file_for_tarfile(descriptor_file, *args, **kwargs):
@@ -589,7 +570,7 @@ def _descriptor_content(attr = None, exclude = (), header_template = (), footer_
"""
header_content, footer_content = [], []
- attr = {} if attr is None else OrderedDict(attr) # shallow copy since we're destructive
+ attr = {} if attr is None else collections.OrderedDict(attr) # shallow copy since we're destructive
for content, template in ((header_content, header_template),
(footer_content, footer_template)):
@@ -707,7 +688,7 @@ def _parse_protocol_line(keyword, attribute):
# parses 'protocol' entries like: Cons=1-2 Desc=1-2 DirCache=1 HSDir=1
value = _value(keyword, entries)
- protocols = OrderedDict()
+ protocols = collections.OrderedDict()
for k, v in _mappings_for(keyword, value):
versions = []
@@ -1163,10 +1144,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):
@@ -1240,7 +1218,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:
@@ -1468,7 +1446,7 @@ def _descriptor_components(raw_contents, validate, extra_keywords = (), non_asci
if isinstance(raw_contents, bytes):
raw_contents = stem.util.str_tools._to_unicode(raw_contents)
- entries = OrderedDict()
+ entries = collections.OrderedDict()
extra_entries = [] # entries with a keyword in extra_keywords
remaining_lines = raw_contents.split('\n')
diff --git a/stem/descriptor/bandwidth_file.py b/stem/descriptor/bandwidth_file.py
index 658f02b8..43c43aff 100644
--- a/stem/descriptor/bandwidth_file.py
+++ b/stem/descriptor/bandwidth_file.py
@@ -14,6 +14,7 @@ Parsing for Bandwidth Authority metrics as described in Tor's
.. versionadded:: 1.8.0
"""
+import collections
import datetime
import io
import time
@@ -25,12 +26,6 @@ from stem.descriptor import (
Descriptor,
)
-try:
- # added in python 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
# Four character dividers are allowed for backward compatability, but five is
# preferred.
@@ -175,7 +170,7 @@ def _parse_file(descriptor_file, validate = False, **kwargs):
def _parse_header(descriptor, entries):
- header = OrderedDict()
+ header = collections.OrderedDict()
content = io.BytesIO(descriptor.get_bytes())
content.readline() # skip the first line, which should be the timestamp
@@ -332,7 +327,7 @@ class BandwidthFile(Descriptor):
if sign:
raise NotImplementedError('Signing of %s not implemented' % cls.__name__)
- header = OrderedDict(attr) if attr is not None else OrderedDict()
+ header = collections.OrderedDict(attr) if attr is not None else collections.OrderedDict()
timestamp = header.pop('timestamp', str(int(time.time())))
content = header.pop('content', [])
version = header.get('version', HEADER_DEFAULT.get('version'))
diff --git a/stem/descriptor/collector.py b/stem/descriptor/collector.py
index c2bf8179..7aeb298b 100644
--- a/stem/descriptor/collector.py
+++ b/stem/descriptor/collector.py
@@ -55,7 +55,6 @@ import hashlib
import json
import os
import re
-import shutil
import tempfile
import time
@@ -264,15 +263,9 @@ class File(object):
if self._downloaded_to and os.path.exists(self._downloaded_to):
directory = os.path.dirname(self._downloaded_to)
else:
- # TODO: The following can be replaced with simpler usage of
- # tempfile.TemporaryDirectory when we drop python 2.x support.
-
- tmp_directory = tempfile.mkdtemp()
-
- for desc in self.read(tmp_directory, descriptor_type, start, end, document_handler, timeout, retries):
- yield desc
-
- shutil.rmtree(tmp_directory)
+ with tempfile.TemporaryDirectory() as tmp_directory:
+ for desc in self.read(tmp_directory, descriptor_type, start, end, document_handler, timeout, retries):
+ yield desc
return
diff --git a/stem/descriptor/export.py b/stem/descriptor/export.py
index 48699ca4..35835d7c 100644
--- a/stem/descriptor/export.py
+++ b/stem/descriptor/export.py
@@ -17,13 +17,9 @@ Toolkit for exporting descriptors to other formats.
use this modle please `let me know <https://www.atagar.com/contact/>`_.
"""
+import io
import csv
-try:
- from cStringIO import StringIO
-except ImportError:
- from io import StringIO
-
import stem.descriptor
import stem.prereq
@@ -44,13 +40,13 @@ def export_csv(descriptors, included_fields = (), excluded_fields = (), header =
:param list included_fields: attributes to include in the csv
:param list excluded_fields: attributes to exclude from the csv
:param bool header: if **True** then the first line will be a comma separated
- list of the attribute names (**only supported in python 2.7 and higher**)
+ list of the attribute names
:returns: **str** of the CSV for the descriptors, one per line
:raises: **ValueError** if descriptors contain more than one descriptor type
"""
- output_buffer = StringIO()
+ output_buffer = io.StringIO()
export_csv_file(output_buffer, descriptors, included_fields, excluded_fields, header)
return output_buffer.getvalue()
@@ -66,7 +62,7 @@ def export_csv_file(output_file, descriptors, included_fields = (), excluded_fie
:param list included_fields: attributes to include in the csv
:param list excluded_fields: attributes to exclude from the csv
:param bool header: if **True** then the first line will be a comma separated
- list of the attribute names (**only supported in python 2.7 and higher**)
+ list of the attribute names
:returns: **str** of the CSV for the descriptors, one per line
:raises: **ValueError** if descriptors contain more than one descriptor type
@@ -103,7 +99,7 @@ def export_csv_file(output_file, descriptors, included_fields = (), excluded_fie
writer = csv.DictWriter(output_file, included_fields, dialect = _ExportDialect(), extrasaction='ignore')
- if header and not stem.prereq._is_python_26():
+ if header:
writer.writeheader()
for desc in descriptors:
diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py
index 17082f88..f53d9502 100644
--- a/stem/descriptor/extrainfo_descriptor.py
+++ b/stem/descriptor/extrainfo_descriptor.py
@@ -100,11 +100,6 @@ from stem.descriptor import (
_random_crypto_blob,
)
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
# known statuses for dirreq-v2-resp and dirreq-v3-resp...
DirResponse = stem.util.enum.Enum(
('OK', 'ok'),
@@ -950,7 +945,7 @@ class RelayExtraInfoDescriptor(ExtraInfoDescriptor):
def create(cls, attr = None, exclude = (), validate = True, sign = False, signing_key = None):
return cls(cls.content(attr, exclude, sign, signing_key), validate = validate)
- @lru_cache()
+ @functools.lru_cache()
def digest(self, hash_type = DigestHash.SHA1, encoding = DigestEncoding.HEX):
if hash_type == DigestHash.SHA1:
# our digest is calculated from everything except our signature
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index e5f82861..7994bffb 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -35,6 +35,7 @@ import base64
import binascii
import collections
import datetime
+import functools
import hashlib
import io
import os
@@ -70,11 +71,6 @@ from stem.descriptor import (
_random_crypto_blob,
)
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
try:
from cryptography.hazmat.backends.openssl.backend import backend
X25519_AVAILABLE = hasattr(backend, 'x25519_supported') and backend.x25519_supported()
@@ -745,7 +741,7 @@ class HiddenServiceDescriptorV2(BaseHiddenServiceDescriptor):
else:
self._entries = entries
- @lru_cache()
+ @functools.lru_cache()
def introduction_points(self, authentication_cookie = None):
"""
Provided this service's introduction points.
diff --git a/stem/descriptor/microdescriptor.py b/stem/descriptor/microdescriptor.py
index 81bcb43c..17d06d90 100644
--- a/stem/descriptor/microdescriptor.py
+++ b/stem/descriptor/microdescriptor.py
@@ -64,6 +64,7 @@ Doing the same is trivial with server descriptors...
Microdescriptor - Tor microdescriptor.
"""
+import functools
import hashlib
import stem.exit_policy
@@ -88,11 +89,6 @@ from stem.descriptor.router_status_entry import (
_parse_p_line,
)
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
REQUIRED_FIELDS = (
'onion-key',
)
@@ -305,7 +301,7 @@ class Microdescriptor(Descriptor):
else:
raise NotImplementedError('Microdescriptor digests are only available in sha1 and sha256, not %s' % hash_type)
- @lru_cache()
+ @functools.lru_cache()
def get_annotations(self):
"""
Provides content that appeared prior to the descriptor. If this comes from
diff --git a/stem/descriptor/reader.py b/stem/descriptor/reader.py
index b4cc4279..e75cdb7e 100644
--- a/stem/descriptor/reader.py
+++ b/stem/descriptor/reader.py
@@ -84,14 +84,10 @@ and picks up where it left off if run again...
import mimetypes
import os
+import queue
import tarfile
import threading
-try:
- import queue
-except ImportError:
- import Queue as queue
-
import stem.descriptor
import stem.prereq
import stem.util
@@ -259,7 +255,7 @@ class DescriptorReader(object):
:param bool validate: checks the validity of the descriptor's content if
**True**, skips these checks otherwise
:param bool follow_links: determines if we'll follow symlinks when traversing
- directories (requires python 2.6)
+ directories
:param int buffer_size: descriptors we'll buffer before waiting for some to
be read, this is unbounded if zero
:param str persistence_path: if set we will load and save processed file
@@ -270,7 +266,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
@@ -525,41 +521,31 @@ class DescriptorReader(object):
self._notify_skip_listeners(target, ReadFailed(exc))
def _handle_archive(self, target):
- # TODO: When dropping python 2.6 support go back to using 'with' for
- # tarfiles...
- #
- # http://bugs.python.org/issue7232
-
- tar_file = None
-
try:
- self._notify_read_listeners(target)
- tar_file = tarfile.open(target)
-
- for tar_entry in tar_file:
- if tar_entry.isfile():
- entry = tar_file.extractfile(tar_entry)
-
- try:
- for desc in stem.descriptor.parse_file(entry, validate = self._validate, document_handler = self._document_handler, **self._kwargs):
- if self._is_stopped.is_set():
- return
-
- desc._set_path(os.path.abspath(target))
- desc._set_archive_path(tar_entry.name)
- self._unreturned_descriptors.put(desc)
- self._iter_notice.set()
- except TypeError as exc:
- self._notify_skip_listeners(target, ParsingFailure(exc))
- except ValueError as exc:
- self._notify_skip_listeners(target, ParsingFailure(exc))
- finally:
- entry.close()
+ with tarfile.open(target) as tar_file:
+ self._notify_read_listeners(target)
+
+ for tar_entry in tar_file:
+ if tar_entry.isfile():
+ entry = tar_file.extractfile(tar_entry)
+
+ try:
+ for desc in stem.descriptor.parse_file(entry, validate = self._validate, document_handler = self._document_handler, **self._kwargs):
+ if self._is_stopped.is_set():
+ return
+
+ desc._set_path(os.path.abspath(target))
+ desc._set_archive_path(tar_entry.name)
+ self._unreturned_descriptors.put(desc)
+ self._iter_notice.set()
+ except TypeError as exc:
+ self._notify_skip_listeners(target, ParsingFailure(exc))
+ except ValueError as exc:
+ self._notify_skip_listeners(target, ParsingFailure(exc))
+ finally:
+ entry.close()
except IOError as exc:
self._notify_skip_listeners(target, ReadFailed(exc))
- finally:
- if tar_file:
- tar_file.close()
def _notify_read_listeners(self, path):
for listener in self._read_listeners:
diff --git a/stem/descriptor/remote.py b/stem/descriptor/remote.py
index c88b338e..c7132a38 100644
--- a/stem/descriptor/remote.py
+++ b/stem/descriptor/remote.py
@@ -104,6 +104,7 @@ import socket
import sys
import threading
import time
+import urllib.request
import stem
import stem.client
@@ -116,12 +117,6 @@ import stem.util.tor_tools
from stem.util import log, str_tools
-try:
- # account for urllib's change between python 2.x and 3.x
- import urllib.request as urllib
-except ImportError:
- import urllib2 as urllib
-
# TODO: remove in stem 2.x, replaced with stem.descriptor.Compression
Compression = stem.util.enum.Enum(
@@ -1070,8 +1065,8 @@ def _download_from_dirport(url, compression, timeout):
"""
try:
- response = urllib.urlopen(
- urllib.Request(
+ response = urllib.request.urlopen(
+ urllib.request.Request(
url,
headers = {
'Accept-Encoding': ', '.join(map(lambda c: c.encoding, compression)),
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/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index 1d9adf32..84ba8b65 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -92,11 +92,6 @@ from stem.descriptor import (
_random_crypto_blob,
)
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
# relay descriptors must have exactly one of the following
REQUIRED_FIELDS = (
'router',
@@ -668,7 +663,7 @@ class ServerDescriptor(Descriptor):
raise NotImplementedError('Unsupported Operation: this should be implemented by the ServerDescriptor subclass')
- @lru_cache()
+ @functools.lru_cache()
def get_annotations(self):
"""
Provides content that appeared prior to the descriptor. If this comes from
@@ -910,7 +905,7 @@ class RelayDescriptor(ServerDescriptor):
def create(cls, attr = None, exclude = (), validate = True, sign = False, signing_key = None, exit_policy = None):
return cls(cls.content(attr, exclude, sign, signing_key, exit_policy), validate = validate, skip_crypto_validation = not sign)
- @lru_cache()
+ @functools.lru_cache()
def digest(self, hash_type = DigestHash.SHA1, encoding = DigestEncoding.HEX):
"""
Provides the digest of our descriptor's content.
@@ -967,7 +962,7 @@ class RelayDescriptor(ServerDescriptor):
return RouterStatusEntryV3.create(attr)
- @lru_cache()
+ @functools.lru_cache()
def _onion_key_crosscert_digest(self):
"""
Provides the digest of the onion-key-crosscert data. This consists of the
@@ -1051,7 +1046,7 @@ class BridgeDescriptor(ServerDescriptor):
return self.get_scrubbing_issues() == []
- @lru_cache()
+ @functools.lru_cache()
def get_scrubbing_issues(self):
"""
Provides issues with our scrubbing.
diff --git a/stem/directory.py b/stem/directory.py
index b93b49a2..7a1ce7b8 100644
--- a/stem/directory.py
+++ b/stem/directory.py
@@ -38,9 +38,11 @@ as follows...
.. versionadded:: 1.7.0
"""
+import collections
import os
import re
import sys
+import urllib.request
import stem
import stem.util
@@ -48,18 +50,6 @@ import stem.util.conf
from stem.util import connection, str_tools, tor_tools
-try:
- # added in python 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
-try:
- # account for urllib's change between python 2.x and 3.x
- import urllib.request as urllib
-except ImportError:
- import urllib2 as urllib
-
GITWEB_AUTHORITY_URL = 'https://gitweb.torproject.org/tor.git/plain/src/app/config/auth_dirs.inc'
GITWEB_FALLBACK_URL = 'https://gitweb.torproject.org/tor.git/plain/src/app/config/fallback_dirs.inc'
FALLBACK_CACHE_PATH = os.path.join(os.path.dirname(__file__), 'cached_fallbacks.cfg')
@@ -265,7 +255,7 @@ class Authority(Directory):
@staticmethod
def from_remote(timeout = 60):
try:
- lines = str_tools._to_unicode(urllib.urlopen(GITWEB_AUTHORITY_URL, timeout = timeout).read()).splitlines()
+ lines = str_tools._to_unicode(urllib.request.urlopen(GITWEB_AUTHORITY_URL, timeout = timeout).read()).splitlines()
if not lines:
raise IOError('no content')
@@ -369,13 +359,13 @@ class Fallback(Directory):
def __init__(self, address = None, or_port = None, dir_port = None, fingerprint = None, nickname = None, has_extrainfo = False, orport_v6 = None, header = None):
super(Fallback, self).__init__(address, or_port, dir_port, fingerprint, nickname, orport_v6)
self.has_extrainfo = has_extrainfo
- self.header = OrderedDict(header) if header else OrderedDict()
+ self.header = collections.OrderedDict(header) if header else collections.OrderedDict()
@staticmethod
def from_cache(path = FALLBACK_CACHE_PATH):
conf = stem.util.conf.Config()
conf.load(path)
- headers = OrderedDict([(k.split('.', 1)[1], conf.get(k)) for k in conf.keys() if k.startswith('header.')])
+ headers = collections.OrderedDict([(k.split('.', 1)[1], conf.get(k)) for k in conf.keys() if k.startswith('header.')])
results = {}
@@ -413,7 +403,7 @@ class Fallback(Directory):
@staticmethod
def from_remote(timeout = 60):
try:
- lines = str_tools._to_unicode(urllib.urlopen(GITWEB_FALLBACK_URL, timeout = timeout).read()).splitlines()
+ lines = str_tools._to_unicode(urllib.request.urlopen(GITWEB_FALLBACK_URL, timeout = timeout).read()).splitlines()
if not lines:
raise IOError('no content')
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index ddaf719c..0d1e7e42 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -67,6 +67,7 @@ exiting to a destination is permissible or not. For instance...
from __future__ import absolute_import
+import functools
import re
import socket
import zlib
@@ -77,11 +78,6 @@ import stem.util.connection
import stem.util.enum
import stem.util.str_tools
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
AddressType = stem.util.enum.Enum(('WILDCARD', 'Wildcard'), ('IPv4', 'IPv4'), ('IPv6', 'IPv6'))
# Addresses aliased by the 'private' policy. From the tor man page...
@@ -128,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 = []
@@ -242,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
@@ -253,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:
@@ -271,7 +267,7 @@ class ExitPolicy(object):
self._is_allowed_default = True
- @lru_cache()
+ @functools.lru_cache()
def can_exit_to(self, address = None, port = None, strict = False):
"""
Checks if this policy allows exiting to a given destination or not. If the
@@ -295,7 +291,7 @@ class ExitPolicy(object):
return self._is_allowed_default
- @lru_cache()
+ @functools.lru_cache()
def is_exiting_allowed(self):
"""
Provides **True** if the policy allows exiting whatsoever, **False**
@@ -317,7 +313,7 @@ class ExitPolicy(object):
return self._is_allowed_default
- @lru_cache()
+ @functools.lru_cache()
def summary(self):
"""
Provides a short description of our policy chain, similar to a
@@ -470,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
@@ -520,7 +516,7 @@ class ExitPolicy(object):
for rule in self._get_rules():
yield rule
- @lru_cache()
+ @functools.lru_cache()
def __str__(self):
return ', '.join([str(rule) for rule in self._get_rules()])
@@ -873,7 +869,7 @@ class ExitPolicyRule(object):
return self._is_default_suffix
- @lru_cache()
+ @functools.lru_cache()
def __str__(self):
"""
Provides the string representation of our policy. This does not
@@ -917,13 +913,13 @@ class ExitPolicyRule(object):
return label
- @lru_cache()
+ @functools.lru_cache()
def _get_mask_bin(self):
# provides an integer representation of our mask
return int(stem.util.connection._address_to_binary(self.get_mask(False)), 2)
- @lru_cache()
+ @functools.lru_cache()
def _get_address_bin(self):
# provides an integer representation of our address
diff --git a/stem/interpreter/__init__.py b/stem/interpreter/__init__.py
index 30af3f62..67984261 100644
--- a/stem/interpreter/__init__.py
+++ b/stem/interpreter/__init__.py
@@ -127,14 +127,7 @@ def main():
if args.run_cmd:
if args.run_cmd.upper().startswith('SETEVENTS '):
- # TODO: we can use a lambda here when dropping python 2.x support, but
- # until then print's status as a keyword prevents it from being used in
- # lambdas
-
- def handle_event(event_message):
- print(format(str(event_message), *STANDARD_OUTPUT))
-
- controller._handle_event = handle_event
+ controller._handle_event = lambda event_message: print(format(str(event_message), *STANDARD_OUTPUT))
if sys.stdout.isatty():
events = args.run_cmd.upper().split(' ', 1)[1]
@@ -171,14 +164,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/interpreter/autocomplete.py b/stem/interpreter/autocomplete.py
index b6c5354c..05585b48 100644
--- a/stem/interpreter/autocomplete.py
+++ b/stem/interpreter/autocomplete.py
@@ -5,15 +5,12 @@
Tab completion for our interpreter prompt.
"""
+import functools
+
import stem.prereq
from stem.interpreter import uses_settings
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
@uses_settings
def _get_commands(controller, config):
@@ -84,7 +81,7 @@ class Autocompleter(object):
def __init__(self, controller):
self._commands = _get_commands(controller)
- @lru_cache()
+ @functools.lru_cache()
def matches(self, text):
"""
Provides autocompletion matches for the given text.
diff --git a/stem/interpreter/commands.py b/stem/interpreter/commands.py
index 0f8f333c..6e61fdda 100644
--- a/stem/interpreter/commands.py
+++ b/stem/interpreter/commands.py
@@ -7,6 +7,7 @@ Handles making requests and formatting the responses.
import code
import contextlib
+import io
import socket
import sys
@@ -21,11 +22,6 @@ import stem.util.tor_tools
from stem.interpreter import STANDARD_OUTPUT, BOLD_OUTPUT, ERROR_OUTPUT, uses_settings, msg
from stem.util.term import format
-try:
- from cStringIO import StringIO
-except ImportError:
- from io import StringIO
-
MAX_EVENTS = 100
@@ -359,7 +355,7 @@ class ControlInterpreter(code.InteractiveConsole):
is_tor_command = cmd in config.get('help.usage', {}) and cmd.lower() != 'events'
if self._run_python_commands and not is_tor_command:
- console_output = StringIO()
+ console_output = io.StringIO()
with redirect(console_output, console_output):
self.is_multiline_context = code.InteractiveConsole.push(self, command)
diff --git a/stem/interpreter/help.py b/stem/interpreter/help.py
index d2e08d5c..5fde9246 100644
--- a/stem/interpreter/help.py
+++ b/stem/interpreter/help.py
@@ -5,6 +5,8 @@
Provides our /help responses.
"""
+import functools
+
import stem.prereq
from stem.interpreter import (
@@ -17,11 +19,6 @@ from stem.interpreter import (
from stem.util.term import format
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
def response(controller, arg):
"""
@@ -55,7 +52,7 @@ def _normalize(arg):
return arg
-@lru_cache()
+@functools.lru_cache()
@uses_settings
def _response(controller, arg, config):
if not arg:
diff --git a/stem/manual.py b/stem/manual.py
index 25d435d4..c1b4bd8f 100644
--- a/stem/manual.py
+++ b/stem/manual.py
@@ -48,10 +48,13 @@ us what our torrc options do...
.. versionadded:: 1.5.0
"""
+import collections
+import functools
import os
import shutil
import sys
import tempfile
+import urllib.request
import stem
import stem.prereq
@@ -61,23 +64,6 @@ import stem.util.enum
import stem.util.log
import stem.util.system
-try:
- # added in python 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
-try:
- # account for urllib's change between python 2.x and 3.x
- import urllib.request as urllib
-except ImportError:
- import urllib2 as urllib
-
Category = stem.util.enum.Enum('GENERAL', 'CLIENT', 'RELAY', 'DIRECTORY', 'AUTHORITY', 'HIDDEN_SERVICE', 'DENIAL_OF_SERVICE', 'TESTING', 'UNKNOWN')
GITWEB_MANUAL_URL = 'https://gitweb.torproject.org/tor.git/plain/doc/tor.1.txt'
CACHE_PATH = os.path.join(os.path.dirname(__file__), 'cached_manual.sqlite')
@@ -96,7 +82,7 @@ SCHEMA = (
'CREATE TABLE torrc(key TEXT PRIMARY KEY, name TEXT, category TEXT, usage TEXT, summary TEXT, description TEXT, position INTEGER)',
)
-CATEGORY_SECTIONS = OrderedDict((
+CATEGORY_SECTIONS = collections.OrderedDict((
('GENERAL OPTIONS', Category.GENERAL),
('CLIENT OPTIONS', Category.CLIENT),
('SERVER OPTIONS', Category.RELAY),
@@ -199,7 +185,7 @@ class ConfigOption(object):
return not self == other
-@lru_cache()
+@functools.lru_cache()
def _config(lowercase = True):
"""
Provides a dictionary for our settings.cfg. This has a couple categories...
@@ -302,14 +288,13 @@ def download_man_page(path = None, file_handle = None, url = GITWEB_MANUAL_URL,
elif not stem.util.system.is_available('a2x'):
raise IOError('We require a2x from asciidoc to provide a man page')
- dirpath = tempfile.mkdtemp()
- asciidoc_path = os.path.join(dirpath, 'tor.1.txt')
- manual_path = os.path.join(dirpath, 'tor.1')
+ with tempfile.TemporaryDirectory() as dirpath:
+ asciidoc_path = os.path.join(dirpath, 'tor.1.txt')
+ manual_path = os.path.join(dirpath, 'tor.1')
- try:
try:
with open(asciidoc_path, 'wb') as asciidoc_file:
- request = urllib.urlopen(url, timeout = timeout)
+ request = urllib.request.urlopen(url, timeout = timeout)
shutil.copyfileobj(request, asciidoc_file)
except:
exc, stacktrace = sys.exc_info()[1:3]
@@ -339,8 +324,6 @@ def download_man_page(path = None, file_handle = None, url = GITWEB_MANUAL_URL,
with open(manual_path, 'rb') as manual_file:
shutil.copyfileobj(manual_file, file_handle)
file_handle.flush()
- finally:
- shutil.rmtree(dirpath)
class Manual(object):
@@ -374,10 +357,10 @@ class Manual(object):
self.name = name
self.synopsis = synopsis
self.description = description
- self.commandline_options = OrderedDict(commandline_options)
- self.signals = OrderedDict(signals)
- self.files = OrderedDict(files)
- self.config_options = OrderedDict(config_options)
+ self.commandline_options = collections.OrderedDict(commandline_options)
+ self.signals = collections.OrderedDict(signals)
+ self.files = collections.OrderedDict(files)
+ self.config_options = collections.OrderedDict(config_options)
self.man_commit = None
self.stem_commit = None
self.schema = None
@@ -442,7 +425,7 @@ class Manual(object):
signals = dict(conn.execute('SELECT name, description FROM signals').fetchall())
files = dict(conn.execute('SELECT name, description FROM files').fetchall())
- config_options = OrderedDict()
+ config_options = collections.OrderedDict()
for entry in conn.execute('SELECT name, category, usage, summary, description FROM torrc ORDER BY position').fetchall():
option, category, usage, summary, option_description = entry
@@ -460,7 +443,7 @@ class Manual(object):
conf = stem.util.conf.Config()
conf.load(path, commenting = False)
- config_options = OrderedDict()
+ config_options = collections.OrderedDict()
for key in conf.keys():
if key.startswith('config_options.'):
@@ -479,9 +462,9 @@ class Manual(object):
conf.get('name', ''),
conf.get('synopsis', ''),
conf.get('description', ''),
- conf.get('commandline_options', OrderedDict()),
- conf.get('signals', OrderedDict()),
- conf.get('files', OrderedDict()),
+ conf.get('commandline_options', collections.OrderedDict()),
+ conf.get('signals', collections.OrderedDict()),
+ conf.get('files', collections.OrderedDict()),
config_options,
)
@@ -514,7 +497,7 @@ class Manual(object):
except OSError as exc:
raise IOError("Unable to run '%s': %s" % (man_cmd, exc))
- categories, config_options = _get_categories(man_output), OrderedDict()
+ categories, config_options = _get_categories(man_output), collections.OrderedDict()
for category_header, category_enum in CATEGORY_SECTIONS.items():
_add_config_options(config_options, category_enum, categories.get(category_header, []))
@@ -677,7 +660,7 @@ def _get_categories(content):
if content and content[-1].startswith('Tor'):
content = content[:-1]
- categories = OrderedDict()
+ categories = collections.OrderedDict()
category, lines = None, []
for line in content:
@@ -687,8 +670,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:
@@ -727,7 +709,7 @@ def _get_indented_descriptions(lines):
ignoring those.
"""
- options, last_arg = OrderedDict(), None
+ options, last_arg = collections.OrderedDict(), None
for line in lines:
if line == ' Note':
diff --git a/stem/prereq.py b/stem/prereq.py
index 5e8e89dc..74584165 100644
--- a/stem/prereq.py
+++ b/stem/prereq.py
@@ -12,12 +12,10 @@ 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
is_lzma_available - checks if the lzma module is available
- is_mock_available - checks if the mock module is available
"""
import functools
@@ -46,52 +44,8 @@ def check_requirements():
major_version, minor_version = sys.version_info[0:2]
- if major_version < 2 or (major_version == 2 and minor_version < 6):
- raise ImportError('stem requires python version 2.6 or greater')
-
-
-def _is_python_26():
- """
- Checks if we're running python 2.6. This isn't for users as it'll be removed
- in stem 2.0 (when python 2.6 support goes away).
-
- .. deprecated:: 1.8.0
- Stem 2.x will remove this method along with Python 2.x support.
-
- :returns: **True** if we're running python 2.6, **False** otherwise
- """
-
- major_version, minor_version = sys.version_info[0:2]
-
- return major_version == 2 and minor_version == 6
-
-
-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
+ if major_version < 3 or (major_version == 3 and minor_version < 6):
+ raise ImportError('stem requires python version 3.6 or greater')
def is_pypy():
@@ -207,63 +161,6 @@ def is_lzma_available():
return False
-def is_mock_available():
- """
- Checks if the mock module is available. In python 3.3 and up it is a builtin
- unittest module, but before this it needed to be `installed separately
- <https://pypi.org/project/mock/>`_. Imports should be as follows....
-
- ::
-
- try:
- # added in python 3.3
- from unittest.mock import Mock
- except ImportError:
- from mock import Mock
-
- :returns: **True** if the mock module is available and **False** otherwise
- """
-
- try:
- # checks for python 3.3 version
- import unittest.mock
- return True
- except ImportError:
- pass
-
- try:
- import mock
-
- # check for mock's patch.dict() which was introduced in version 0.7.0
-
- if not hasattr(mock.patch, 'dict'):
- raise ImportError()
-
- # check for mock's new_callable argument for patch() which was introduced in version 0.8.0
-
- if 'new_callable' not in inspect.getargspec(mock.patch).args:
- raise ImportError()
-
- return True
- except ImportError:
- return False
-
-
-def _is_lru_cache_available():
- """
- Functools added lru_cache to the standard library in Python 3.2. Prior to
- this using a bundled implementation. We're also using this with Python 3.5
- due to a buggy implementation. (:trac:`26412`)
- """
-
- major_version, minor_version = sys.version_info[0:2]
-
- if major_version == 3 and minor_version == 5:
- return False
- else:
- return hasattr(functools, 'lru_cache')
-
-
def _is_sha3_available():
"""
Check if hashlib has sha3 support. This requires Python 3.6+ *or* the `pysha3
diff --git a/stem/process.py b/stem/process.py
index 4ed10dfb..3f1a0e19 100644
--- a/stem/process.py
+++ b/stem/process.py
@@ -143,12 +143,9 @@ def launch_tor(tor_cmd = 'tor', args = None, torrc_path = None, completion_perce
last_problem = 'Timed out'
while True:
- # Tor's stdout will be read as ASCII bytes. This is fine for python 2, but
- # in python 3 that means it'll mismatch with other operations (for instance
- # the bootstrap_line.search() call later will fail).
- #
- # It seems like python 2.x is perfectly happy for this to be unicode, so
- # normalizing to that.
+ # Tor's stdout will be read as ASCII bytes. That means it'll mismatch
+ # with other operations (for instance the bootstrap_line.search() call
+ # later will fail), so normalizing to unicode.
init_line = tor_process.stdout.readline().decode('utf-8', 'replace').strip()
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..4ab534b8 100644
--- a/stem/socket.py
+++ b/stem/socket.py
@@ -204,21 +204,10 @@ class BaseSocket(object):
except socket.error:
pass
- # Suppressing unexpected exceptions from close. For instance, if the
- # socket's file has already been closed then with python 2.7 that raises
- # with...
- # error: [Errno 32] Broken pipe
-
- try:
- self._socket.close()
- except:
- pass
+ self._socket.close()
if self._socket_file:
- try:
- self._socket_file.close()
- except:
- pass
+ self._socket_file.close()
self._socket = None
self._socket_file = None
@@ -680,14 +669,12 @@ def recv_message(control_file, arrived_at = None):
log.info(ERROR_MSG % ('SocketClosed', 'socket file has been closed'))
raise stem.SocketClosed('socket file has been closed')
- except (socket.error, ValueError) as exc:
- # When disconnected we get...
- #
- # Python 2:
- # socket.error: [Errno 107] Transport endpoint is not connected
+ except (OSError, ValueError) as exc:
+ # when disconnected this errors with...
#
- # Python 3:
- # ValueError: I/O operation on closed file.
+ # * ValueError: I/O operation on closed file
+ # * OSError: [Errno 107] Transport endpoint is not connected
+ # * OSError: [Errno 9] Bad file descriptor
log.info(ERROR_MSG % ('SocketClosed', 'received exception "%s"' % exc))
raise stem.SocketClosed(exc)
@@ -710,9 +697,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 5be29777..2bcc8a07 100644
--- a/stem/util/__init__.py
+++ b/stem/util/__init__.py
@@ -49,19 +49,10 @@ def _hash_value(val):
if not HASH_TYPES:
my_hash = 0
else:
- # TODO: I hate doing this but until Python 2.x support is dropped we
- # can't readily be strict about bytes vs unicode for attributes. This
- # is because test assertions often use strings, and normalizing this
- # would require wrapping most with to_unicode() calls.
- #
- # This hack will go away when we drop Python 2.x support.
+ # Hashing common builtins (ints, bools, etc) provide consistant values but
+ # many others vary their value on interpreter invokation.
- if _is_str(val):
- my_hash = hash('str')
- else:
- # Hashing common builtins (ints, bools, etc) provide consistant values but many others vary their value on interpreter invokation.
-
- my_hash = hash(str(type(val)))
+ my_hash = hash(str(type(val)))
if isinstance(val, (tuple, list)):
for v in val:
@@ -75,40 +66,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.
@@ -120,11 +77,7 @@ def datetime_to_unix(timestamp):
:returns: **float** for the unix timestamp of the given datetime object
"""
- if stem.prereq._is_python_26():
- delta = (timestamp - datetime.datetime(1970, 1, 1))
- return delta.days * 86400 + delta.seconds
- else:
- return (timestamp - datetime.datetime(1970, 1, 1)).total_seconds()
+ return (timestamp - datetime.datetime(1970, 1, 1)).total_seconds()
def _pubkey_bytes(key):
@@ -132,7 +85,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 b4580ed9..1dbcc243 100644
--- a/stem/util/conf.py
+++ b/stem/util/conf.py
@@ -157,6 +157,7 @@ Here's an expanation of what happened...
+- get_value - provides the value for a given key as a string
"""
+import collections
import inspect
import os
import threading
@@ -165,12 +166,6 @@ import stem.prereq
from stem.util import log
-try:
- # added in python 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
CONFS = {} # mapping of identifier to singleton instances of configs
@@ -453,9 +448,9 @@ class Config(object):
"""
def __init__(self):
- self._path = None # location we last loaded from or saved to
- self._contents = OrderedDict() # configuration key/value pairs
- self._listeners = [] # functors to be notified of config changes
+ self._path = None # location we last loaded from or saved to
+ self._contents = collections.OrderedDict() # configuration key/value pairs
+ self._listeners = [] # functors to be notified of config changes
# used for accessing _contents
self._contents_lock = threading.RLock()
@@ -640,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:
@@ -735,7 +728,7 @@ class Config(object):
elif isinstance(default, tuple):
val = tuple(val)
elif isinstance(default, dict):
- val_map = OrderedDict()
+ val_map = collections.OrderedDict()
for entry in val:
if '=>' in entry:
entry_key, entry_val = entry.split('=>', 1)
diff --git a/stem/util/connection.py b/stem/util/connection.py
index bd1fb5a2..f88b3f85 100644
--- a/stem/util/connection.py
+++ b/stem/util/connection.py
@@ -62,6 +62,7 @@ import re
import socket
import sys
import time
+import urllib.request
import stem
import stem.util
@@ -70,12 +71,6 @@ import stem.util.system
from stem.util import conf, enum, log, str_tools
-try:
- # account for urllib's change between python 2.x and 3.x
- import urllib.request as urllib
-except ImportError:
- import urllib2 as urllib
-
# Connection resolution is risky to log about since it's highly likely to
# contain sensitive information. That said, it's also difficult to get right in
# a platform independent fashion. To opt into the logging requried to
@@ -197,7 +192,7 @@ def download(url, timeout = None, retries = None):
start_time = time.time()
try:
- return urllib.urlopen(url, timeout = timeout).read()
+ return urllib.request.urlopen(url, timeout = timeout).read()
except socket.timeout as exc:
raise stem.DownloadTimeout(url, exc, sys.exc_info()[2], timeout)
except:
@@ -458,7 +453,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 +483,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..4ef977b1 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)
@@ -232,13 +231,7 @@ class LogBuffer(logging.Handler):
"""
def __init__(self, runlevel, yield_records = False):
- # TODO: At least in python 2.6 logging.Handler has a bug in that it doesn't
- # extend object, causing our super() call to fail. When we drop python 2.6
- # support we should switch back to using super() instead.
- #
- # super(LogBuffer, self).__init__(level = logging_level(runlevel))
-
- logging.Handler.__init__(self, level = logging_level(runlevel))
+ super(LogBuffer, self).__init__(level = logging_level(runlevel))
self.formatter = FORMATTER
self._buffer = []
diff --git a/stem/util/lru_cache.py b/stem/util/lru_cache.py
deleted file mode 100644
index 011d4456..00000000
--- a/stem/util/lru_cache.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# Drop in replace for python 3.2's collections.lru_cache, from...
-# http://code.activestate.com/recipes/578078-py26-and-py30-backport-of-python-33s-lru-cache/
-#
-# ... which is under the MIT license. Stem users should *not* rely upon this
-# module. It will be removed when we drop support for python 3.2 and below.
-
-"""
-Memoization decorator that caches a function's return value. If later called
-with the same arguments then the cached value is returned rather than
-reevaluated.
-
-This is a a python 2.x port of `functools.lru_cache
-<http://docs.python.org/3/library/functools.html#functools.lru_cache>`_. If
-using python 3.2 or later you should use that instead.
-"""
-
-from collections import namedtuple
-from functools import update_wrapper
-from threading import RLock
-
-_CacheInfo = namedtuple('CacheInfo', ['hits', 'misses', 'maxsize', 'currsize'])
-
-
-class _HashedSeq(list):
- __slots__ = 'hashvalue'
-
- def __init__(self, tup, hash=hash):
- self[:] = tup
- self.hashvalue = hash(tup)
-
- def __hash__(self):
- return self.hashvalue
-
-
-def _make_key(args, kwds, typed,
- kwd_mark = (object(),),
- fasttypes = set([int, str, frozenset, type(None)]),
- sorted=sorted, tuple=tuple, type=type, len=len):
- 'Make a cache key from optionally typed positional and keyword arguments'
- key = args
- if kwds:
- sorted_items = sorted(kwds.items())
- key += kwd_mark
- for item in sorted_items:
- key += item
- if typed:
- key += tuple(type(v) for v in args)
- if kwds:
- key += tuple(type(v) for k, v in sorted_items)
- elif len(key) == 1 and type(key[0]) in fasttypes:
- return key[0]
- return _HashedSeq(key)
-
-
-def lru_cache(maxsize=100, typed=False):
- """Least-recently-used cache decorator.
-
- If *maxsize* is set to None, the LRU features are disabled and the cache
- can grow without bound.
-
- If *typed* is True, arguments of different types will be cached separately.
- For example, f(3.0) and f(3) will be treated as distinct calls with
- distinct results.
-
- Arguments to the cached function must be hashable.
-
- View the cache statistics named tuple (hits, misses, maxsize, currsize) with
- f.cache_info(). Clear the cache and statistics with f.cache_clear().
- Access the underlying function with f.__wrapped__.
-
- See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
-
- """
-
- # Users should only access the lru_cache through its public API:
- # cache_info, cache_clear, and f.__wrapped__
- # The internals of the lru_cache are encapsulated for thread safety and
- # to allow the implementation to change (including a possible C version).
-
- def decorating_function(user_function):
-
- cache = dict()
- stats = [0, 0] # make statistics updateable non-locally
- HITS, MISSES = 0, 1 # names for the stats fields
- make_key = _make_key
- cache_get = cache.get # bound method to lookup key or return None
- _len = len # localize the global len() function
- lock = RLock() # because linkedlist updates aren't threadsafe
- root = [] # root of the circular doubly linked list
- root[:] = [root, root, None, None] # initialize by pointing to self
- nonlocal_root = [root] # make updateable non-locally
- PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields
-
- if maxsize == 0:
-
- def wrapper(*args, **kwds):
- # no caching, just do a statistics update after a successful call
- result = user_function(*args, **kwds)
- stats[MISSES] += 1
- return result
-
- elif maxsize is None:
-
- def wrapper(*args, **kwds):
- # simple caching without ordering or size limit
- key = make_key(args, kwds, typed)
- result = cache_get(key, root) # root used here as a unique not-found sentinel
- if result is not root:
- stats[HITS] += 1
- return result
- result = user_function(*args, **kwds)
- cache[key] = result
- stats[MISSES] += 1
- return result
-
- else:
-
- def wrapper(*args, **kwds):
- # size limited caching that tracks accesses by recency
- key = make_key(args, kwds, typed) if kwds or typed else args
- with lock:
- link = cache_get(key)
- if link is not None:
- # record recent use of the key by moving it to the front of the list
- root, = nonlocal_root
- link_prev, link_next, key, result = link
- link_prev[NEXT] = link_next
- link_next[PREV] = link_prev
- last = root[PREV]
- last[NEXT] = root[PREV] = link
- link[PREV] = last
- link[NEXT] = root
- stats[HITS] += 1
- return result
- result = user_function(*args, **kwds)
- with lock:
- root, = nonlocal_root
- if key in cache:
- # getting here means that this same key was added to the
- # cache while the lock was released. since the link
- # update is already done, we need only return the
- # computed result and update the count of misses.
- pass
- elif _len(cache) >= maxsize:
- # use the old root to store the new key and result
- oldroot = root
- oldroot[KEY] = key
- oldroot[RESULT] = result
- # empty the oldest link and make it the new root
- root = nonlocal_root[0] = oldroot[NEXT]
- oldkey = root[KEY]
- root[KEY] = root[RESULT] = None
- # now update the cache dictionary for the new links
- del cache[oldkey]
- cache[key] = oldroot
- else:
- # put result in a new link at the front of the list
- last = root[PREV]
- link = [last, root, key, result]
- last[NEXT] = root[PREV] = cache[key] = link
- stats[MISSES] += 1
- return result
-
- def cache_info():
- """Report cache statistics"""
- with lock:
- return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache))
-
- def cache_clear():
- """Clear the cache and cache statistics"""
- with lock:
- cache.clear()
- root = nonlocal_root[0]
- root[:] = [root, root, None, None]
- stats[:] = [0, 0]
-
- wrapper.__wrapped__ = user_function
- wrapper.cache_info = cache_info
- wrapper.cache_clear = cache_clear
- return update_wrapper(wrapper, user_function)
-
- return decorating_function
diff --git a/stem/util/ordereddict.py b/stem/util/ordereddict.py
deleted file mode 100644
index ec228f31..00000000
--- a/stem/util/ordereddict.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# Drop in replacement for python 2.7's OrderedDict, from...
-# https://pypi.org/project/ordereddict/
-#
-# Stem users should *not* rely upon this module. It will be removed when we
-# drop support for python 2.6 and below.
-
-# Copyright (c) 2009 Raymond Hettinger
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-# OTHER DEALINGS IN THE SOFTWARE.
-
-from UserDict import DictMixin
-
-
-class OrderedDict(dict, DictMixin):
- def __init__(self, *args, **kwds):
- if len(args) > 1:
- raise TypeError('expected at most 1 arguments, got %d' % len(args))
- try:
- self.__end
- except AttributeError:
- self.clear()
- self.update(*args, **kwds)
-
- def clear(self):
- self.__end = end = []
- end += [None, end, end] # sentinel node for doubly linked list
- self.__map = {} # key --> [key, prev, next]
- dict.clear(self)
-
- def __setitem__(self, key, value):
- if key not in self:
- end = self.__end
- curr = end[1]
- curr[2] = end[1] = self.__map[key] = [key, curr, end]
- dict.__setitem__(self, key, value)
-
- def __delitem__(self, key):
- dict.__delitem__(self, key)
- key, prev, next = self.__map.pop(key)
- prev[2] = next
- next[1] = prev
-
- def __iter__(self):
- end = self.__end
- curr = end[2]
- while curr is not end:
- yield curr[0]
- curr = curr[2]
-
- def __reversed__(self):
- end = self.__end
- curr = end[1]
- while curr is not end:
- yield curr[0]
- curr = curr[1]
-
- def popitem(self, last=True):
- if not self:
- raise KeyError('dictionary is empty')
- if last:
- key = reversed(self).next()
- else:
- key = iter(self).next()
- value = self.pop(key)
- return key, value
-
- def __reduce__(self):
- items = [[k, self[k]] for k in self]
- tmp = self.__map, self.__end
- del self.__map, self.__end
- inst_dict = vars(self).copy()
- self.__map, self.__end = tmp
- if inst_dict:
- return (self.__class__, (items,), inst_dict)
- return self.__class__, (items,)
-
- def keys(self):
- return list(self)
-
- setdefault = DictMixin.setdefault
- update = DictMixin.update
- pop = DictMixin.pop
- values = DictMixin.values
- items = DictMixin.items
- iterkeys = DictMixin.iterkeys
- itervalues = DictMixin.itervalues
- iteritems = DictMixin.iteritems
-
- def __repr__(self):
- if not self:
- return '%s()' % (self.__class__.__name__,)
- return '%s(%r)' % (self.__class__.__name__, self.items())
-
- def copy(self):
- return self.__class__(self)
-
- @classmethod
- def fromkeys(cls, iterable, value=None):
- d = cls()
- for key in iterable:
- d[key] = value
- return d
-
- def __eq__(self, other):
- if isinstance(other, OrderedDict):
- if len(self) != len(other):
- return False
- for p, q in zip(self.items(), other.items()):
- if p != q:
- return False
- return True
- return dict.__eq__(self, other)
-
- def __ne__(self, other):
- return not self == other
diff --git a/stem/util/proc.py b/stem/util/proc.py
index f0e0104f..ecb7f3f7 100644
--- a/stem/util/proc.py
+++ b/stem/util/proc.py
@@ -48,6 +48,7 @@ future, use them at your own risk.**
"""
import base64
+import functools
import os
import platform
import socket
@@ -68,11 +69,6 @@ try:
except ImportError:
IS_PWD_AVAILABLE = False
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
# os.sysconf is only defined on unix
try:
CLOCK_TICKS = os.sysconf(os.sysconf_names['SC_CLK_TCK'])
@@ -88,7 +84,7 @@ Stat = stem.util.enum.Enum(
)
-@lru_cache()
+@functools.lru_cache()
def is_available():
"""
Checks if proc information is available on this platform.
@@ -109,7 +105,7 @@ def is_available():
return True
-@lru_cache()
+@functools.lru_cache()
def system_start_time():
"""
Provides the unix time (seconds since epoch) when the system started.
@@ -132,7 +128,7 @@ def system_start_time():
raise exc
-@lru_cache()
+@functools.lru_cache()
def physical_memory():
"""
Provides the total physical memory on the system in bytes.
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/stem/util/test_tools.py b/stem/util/test_tools.py
index c7dce7ce..d65b3ea4 100644
--- a/stem/util/test_tools.py
+++ b/stem/util/test_tools.py
@@ -56,15 +56,6 @@ ASYNC_TESTS = {}
AsyncStatus = stem.util.enum.UppercaseEnum('PENDING', 'RUNNING', 'FINISHED')
AsyncResult = collections.namedtuple('AsyncResult', 'type msg')
-# TODO: Providing a copy of SkipTest that works with python 2.6. This will be
-# dropped when we remove python 2.6 support.
-
-if stem.prereq._is_python_26():
- class SkipTest(Exception):
- 'Notes that the test was skipped.'
-else:
- SkipTest = unittest.case.SkipTest
-
def assert_equal(expected, actual, msg = None):
"""
@@ -111,7 +102,7 @@ def skip(msg):
:raises: **unittest.case.SkipTest** for this reason
"""
- raise SkipTest(msg)
+ raise unittest.case.SkipTest(msg)
def asynchronous(func):
@@ -159,9 +150,6 @@ class AsyncTest(object):
self._status = AsyncStatus.PENDING
def run(self, *runner_args, **kwargs):
- if stem.prereq._is_python_26():
- return # not supported under python 2.6
-
def _wrapper(conn, runner, args):
os.nice(12)
@@ -170,7 +158,7 @@ class AsyncTest(object):
conn.send(AsyncResult('success', None))
except AssertionError as exc:
conn.send(AsyncResult('failure', str(exc)))
- except SkipTest as exc:
+ except unittest.case.SkipTest as exc:
conn.send(AsyncResult('skipped', str(exc)))
except:
conn.send(AsyncResult('error', traceback.format_exc()))
@@ -209,9 +197,6 @@ class AsyncTest(object):
self.result(None)
def result(self, test):
- if stem.prereq._is_python_26():
- return # not supported under python 2.6
-
with self._process_lock:
if self._status == AsyncStatus.PENDING:
self.run()
@@ -259,21 +244,6 @@ class TimedTestRunner(unittest.TextTestRunner):
TEST_RUNTIMES[self.id()] = time.time() - start_time
return result
- # TODO: remove and drop unnecessary 'returns' when dropping python 2.6
- # support
-
- def skipTest(self, message):
- if not stem.prereq._is_python_26():
- return super(original_type, self).skipTest(message)
-
- # TODO: remove when dropping python 2.6 support
-
- def assertItemsEqual(self, expected, actual):
- if stem.prereq._is_python_26():
- self.assertEqual(set(expected), set(actual))
- else:
- return super(original_type, self).assertItemsEqual(expected, actual)
-
def assertRaisesWith(self, exc_type, exc_msg, func, *args, **kwargs):
"""
Asserts the given invokation raises the expected excepiton. This is
@@ -287,16 +257,6 @@ class TimedTestRunner(unittest.TextTestRunner):
return self.assertRaisesRegexp(exc_type, '^%s$' % re.escape(exc_msg), func, *args, **kwargs)
- def assertRaisesRegexp(self, exc_type, exc_msg, func, *args, **kwargs):
- if stem.prereq._is_python_26():
- try:
- func(*args, **kwargs)
- self.fail('Expected a %s to be raised but nothing was' % exc_type)
- except exc_type as exc:
- self.assertTrue(re.search(exc_msg, str(exc), re.MULTILINE))
- else:
- return super(original_type, self).assertRaisesRegexp(exc_type, exc_msg, func, *args, **kwargs)
-
def id(self):
return '%s.%s.%s' % (original_type.__module__, original_type.__name__, self._testMethodName)
@@ -511,21 +471,6 @@ def stylistic_issues(paths, check_newlines = False, check_exception_keyword = Fa
if '"""' in content:
is_block_comment = not is_block_comment
- if check_exception_keyword and content.startswith('except') and content.endswith(', exc:'):
- # Python 2.6 - 2.7 supports two forms for exceptions...
- #
- # except ValueError, exc:
- # except ValueError as exc:
- #
- # The former is the old method and no longer supported in python 3
- # going forward.
-
- # TODO: This check only works if the exception variable is called
- # 'exc'. We should generalize this via a regex so other names work
- # too.
-
- issues.setdefault(filename, []).append(Issue(index + 1, "except clause should use 'as', not comma", line))
-
if prefer_single_quotes and not is_block_comment:
if '"' in content and "'" not in content and '"""' not in content and not content.endswith('\\'):
# Checking if the line already has any single quotes since that
diff --git a/stem/version.py b/stem/version.py
index 6bf0befe..71f16e2c 100644
--- a/stem/version.py
+++ b/stem/version.py
@@ -84,6 +84,7 @@ easily parsed and compared, for instance...
===================================== ===========
"""
+import functools
import os
import re
@@ -92,11 +93,6 @@ import stem.util
import stem.util.enum
import stem.util.system
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
# cache for the get_system_tor_version function
VERSION_CACHE = {}
@@ -150,7 +146,7 @@ def get_system_tor_version(tor_cmd = 'tor'):
return VERSION_CACHE[tor_cmd]
-@lru_cache()
+@functools.lru_cache()
def _get_version(version_str):
return Version(version_str)
diff --git a/test/integ/connection/authentication.py b/test/integ/connection/authentication.py
index 1ab6aae0..cd562e7b 100644
--- a/test/integ/connection/authentication.py
+++ b/test/integ/connection/authentication.py
@@ -280,7 +280,6 @@ class TestAuthenticate(unittest.TestCase):
if test.runner.Torrc.PASSWORD not in runner.get_options() or test.runner.Torrc.COOKIE in runner.get_options():
self.skipTest('(requires only password auth)')
- return
for i in range(10):
with runner.get_tor_controller(False) as controller:
diff --git a/test/integ/connection/connect.py b/test/integ/connection/connect.py
index f4df233f..84b4a8fc 100644
--- a/test/integ/connection/connect.py
+++ b/test/integ/connection/connect.py
@@ -2,27 +2,19 @@
Integration tests for the connect_* convenience functions.
"""
+import io
import unittest
import stem.connection
import test.require
import test.runner
-try:
- from StringIO import StringIO
-except ImportError:
- from io import StringIO
-
-try:
- # added in python 3.3
- from unittest.mock import patch
-except ImportError:
- from mock import patch
+from unittest.mock import patch
class TestConnect(unittest.TestCase):
@test.require.controller
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
def test_connect(self, stdout_mock):
"""
Basic sanity checks for the connect function.
@@ -41,7 +33,7 @@ class TestConnect(unittest.TestCase):
self.assertEqual('', stdout_mock.getvalue())
@test.require.controller
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
def test_connect_port(self, stdout_mock):
"""
Basic sanity checks for the connect_port function.
@@ -63,7 +55,7 @@ class TestConnect(unittest.TestCase):
self.assertEqual(control_socket, None)
@test.require.controller
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
def test_connect_socket_file(self, stdout_mock):
"""
Basic sanity checks for the connect_socket_file function.
@@ -85,7 +77,7 @@ class TestConnect(unittest.TestCase):
self.assertEqual(control_socket, None)
@test.require.controller
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
def test_connect_to_socks_port(self, stdout_mock):
"""
Common user gotcha is connecting to the SocksPort or ORPort rather than the
diff --git a/test/integ/control/base_controller.py b/test/integ/control/base_controller.py
index 294eaee3..323b57c7 100644
--- a/test/integ/control/base_controller.py
+++ b/test/integ/control/base_controller.py
@@ -47,7 +47,6 @@ class TestBaseController(unittest.TestCase):
if stem.util.system.is_mac():
self.skipTest('(ticket #6235)')
- return
with test.runner.get_runner().get_tor_socket() as control_socket:
controller = stem.control.BaseController(control_socket)
@@ -97,7 +96,6 @@ class TestBaseController(unittest.TestCase):
if stem.util.system.is_mac():
self.skipTest('(ticket #6235)')
- return
with test.runner.get_runner().get_tor_socket() as control_socket:
controller = stem.control.BaseController(control_socket)
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index a32f66f0..257d9fbc 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -754,71 +754,70 @@ class TestController(unittest.TestCase):
"""
runner = test.runner.get_runner()
- tmpdir = tempfile.mkdtemp()
- with runner.get_tor_controller() as controller:
- try:
- # successfully set a single option
- connlimit = int(controller.get_conf('ConnLimit'))
- controller.set_conf('connlimit', str(connlimit - 1))
- self.assertEqual(connlimit - 1, int(controller.get_conf('ConnLimit')))
-
- # successfully set a single list option
- exit_policy = ['accept *:7777', 'reject *:*']
- controller.set_conf('ExitPolicy', exit_policy)
- self.assertEqual(exit_policy, controller.get_conf('ExitPolicy', multiple = True))
+ with tempfile.TemporaryDirectory() as tmpdir:
- # fail to set a single option
+ with runner.get_tor_controller() as controller:
try:
- controller.set_conf('invalidkeyboo', 'abcde')
- self.fail()
- except stem.InvalidArguments as exc:
- self.assertEqual(['invalidkeyboo'], exc.arguments)
+ # successfully set a single option
+ connlimit = int(controller.get_conf('ConnLimit'))
+ controller.set_conf('connlimit', str(connlimit - 1))
+ self.assertEqual(connlimit - 1, int(controller.get_conf('ConnLimit')))
- # resets configuration parameters
- controller.reset_conf('ConnLimit', 'ExitPolicy')
- self.assertEqual(connlimit, int(controller.get_conf('ConnLimit')))
- self.assertEqual(None, controller.get_conf('ExitPolicy'))
+ # successfully set a single list option
+ exit_policy = ['accept *:7777', 'reject *:*']
+ controller.set_conf('ExitPolicy', exit_policy)
+ self.assertEqual(exit_policy, controller.get_conf('ExitPolicy', multiple = True))
- # successfully sets multiple config options
- controller.set_options({
- 'connlimit': str(connlimit - 2),
- 'contactinfo': 'stem@testing',
- })
+ # fail to set a single option
+ try:
+ controller.set_conf('invalidkeyboo', 'abcde')
+ self.fail()
+ except stem.InvalidArguments as exc:
+ self.assertEqual(['invalidkeyboo'], exc.arguments)
- self.assertEqual(connlimit - 2, int(controller.get_conf('ConnLimit')))
- self.assertEqual('stem@testing', controller.get_conf('contactinfo'))
+ # resets configuration parameters
+ controller.reset_conf('ConnLimit', 'ExitPolicy')
+ self.assertEqual(connlimit, int(controller.get_conf('ConnLimit')))
+ self.assertEqual(None, controller.get_conf('ExitPolicy'))
- # fail to set multiple config options
- try:
+ # successfully sets multiple config options
controller.set_options({
+ 'connlimit': str(connlimit - 2),
'contactinfo': 'stem@testing',
- 'bombay': 'vadapav',
})
- self.fail()
- except stem.InvalidArguments as exc:
- self.assertEqual(['bombay'], exc.arguments)
- # context-sensitive keys (the only retched things for which order matters)
- controller.set_options((
- ('HiddenServiceDir', tmpdir),
- ('HiddenServicePort', '17234 127.0.0.1:17235'),
- ))
-
- self.assertEqual(tmpdir, controller.get_conf('HiddenServiceDir'))
- self.assertEqual('17234 127.0.0.1:17235', controller.get_conf('HiddenServicePort'))
- finally:
- # reverts configuration changes
+ self.assertEqual(connlimit - 2, int(controller.get_conf('ConnLimit')))
+ self.assertEqual('stem@testing', controller.get_conf('contactinfo'))
- controller.set_options((
- ('ExitPolicy', 'reject *:*'),
- ('ConnLimit', None),
- ('ContactInfo', None),
- ('HiddenServiceDir', None),
- ('HiddenServicePort', None),
- ), reset = True)
+ # fail to set multiple config options
+ try:
+ controller.set_options({
+ 'contactinfo': 'stem@testing',
+ 'bombay': 'vadapav',
+ })
+ self.fail()
+ except stem.InvalidArguments as exc:
+ self.assertEqual(['bombay'], exc.arguments)
+
+ # context-sensitive keys (the only retched things for which order matters)
+ controller.set_options((
+ ('HiddenServiceDir', tmpdir),
+ ('HiddenServicePort', '17234 127.0.0.1:17235'),
+ ))
+
+ self.assertEqual(tmpdir, controller.get_conf('HiddenServiceDir'))
+ self.assertEqual('17234 127.0.0.1:17235', controller.get_conf('HiddenServicePort'))
+ finally:
+ # reverts configuration changes
- shutil.rmtree(tmpdir)
+ controller.set_options((
+ ('ExitPolicy', 'reject *:*'),
+ ('ConnLimit', None),
+ ('ContactInfo', None),
+ ('HiddenServiceDir', None),
+ ('HiddenServicePort', None),
+ ), reset = True)
@test.require.controller
def test_set_conf_for_usebridges(self):
@@ -1296,7 +1295,6 @@ class TestController(unittest.TestCase):
if not os.path.exists(runner.get_test_dir('cached-microdescs')):
self.skipTest('(no cached microdescriptors)')
- return
with runner.get_tor_controller() as controller:
count = 0
@@ -1318,7 +1316,6 @@ class TestController(unittest.TestCase):
if test.tor_version() >= Requirement.MICRODESCRIPTOR_IS_DEFAULT:
self.skipTest('(requires server descriptors)')
- return
with runner.get_tor_controller() as controller:
# we should balk at invalid content
@@ -1349,7 +1346,6 @@ class TestController(unittest.TestCase):
if test.tor_version() >= Requirement.MICRODESCRIPTOR_IS_DEFAULT:
self.skipTest('(requires server descriptors)')
- return
with runner.get_tor_controller() as controller:
count = 0
@@ -1534,6 +1530,5 @@ class TestController(unittest.TestCase):
if TEST_ROUTER_STATUS_ENTRY is None:
# this is only likely to occure if we can't get descriptors
self.skipTest('(no named relays)')
- return
return TEST_ROUTER_STATUS_ENTRY
diff --git a/test/integ/descriptor/collector.py b/test/integ/descriptor/collector.py
index 3af25c29..53f45ae5 100644
--- a/test/integ/descriptor/collector.py
+++ b/test/integ/descriptor/collector.py
@@ -93,7 +93,6 @@ class TestCollector(unittest.TestCase):
def _test_index(self, compression):
if compression and not compression.available:
self.skipTest('(%s unavailable)' % compression)
- return
collector = stem.descriptor.collector.CollecTor()
index = collector.index(compression = compression)
diff --git a/test/integ/descriptor/extrainfo_descriptor.py b/test/integ/descriptor/extrainfo_descriptor.py
index c0a4d30c..4bfb8f3f 100644
--- a/test/integ/descriptor/extrainfo_descriptor.py
+++ b/test/integ/descriptor/extrainfo_descriptor.py
@@ -28,7 +28,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
descriptor_path = os.path.join(test_dir, 'cached-extrainfo')
if not os.path.exists(descriptor_path):
- raise stem.util.test_tools.SkipTest('(no cached descriptors)')
+ raise unittest.case.SkipTest('(no cached descriptors)')
with open(descriptor_path, 'rb') as descriptor_file:
for desc in stem.descriptor.parse_file(descriptor_file, 'extra-info 1.0', validate = True):
diff --git a/test/integ/descriptor/microdescriptor.py b/test/integ/descriptor/microdescriptor.py
index 3f2b20cb..4e67e15b 100644
--- a/test/integ/descriptor/microdescriptor.py
+++ b/test/integ/descriptor/microdescriptor.py
@@ -28,7 +28,7 @@ class TestMicrodescriptor(unittest.TestCase):
descriptor_path = os.path.join(test_dir, 'cached-microdescs')
if not os.path.exists(descriptor_path):
- raise stem.util.test_tools.SkipTest('(no cached descriptors)')
+ raise unittest.case.SkipTest('(no cached descriptors)')
with open(descriptor_path, 'rb') as descriptor_file:
for desc in stem.descriptor.parse_file(descriptor_file, 'microdescriptor 1.0', validate = True):
diff --git a/test/integ/descriptor/networkstatus.py b/test/integ/descriptor/networkstatus.py
index b9684e6f..efe646f9 100644
--- a/test/integ/descriptor/networkstatus.py
+++ b/test/integ/descriptor/networkstatus.py
@@ -43,12 +43,12 @@ class TestNetworkStatus(unittest.TestCase):
consensus_path = os.path.join(test_dir, 'cached-consensus')
if not os.path.exists(consensus_path):
- raise stem.util.test_tools.SkipTest('(no cached-consensus)')
+ raise unittest.case.SkipTest('(no cached-consensus)')
elif stem.util.system.is_windows():
# Unable to check memory usage on windows, so can't prevent hanging the
# system if things go bad.
- raise stem.util.test_tools.SkipTest('(unavailable on windows)')
+ raise unittest.case.SkipTest('(unavailable on windows)')
count, reported_flags = 0, []
@@ -79,9 +79,9 @@ class TestNetworkStatus(unittest.TestCase):
consensus_path = os.path.join(test_dir, 'cached-microdesc-consensus')
if not os.path.exists(consensus_path):
- raise stem.util.test_tools.SkipTest('(no cached-microdesc-consensus)')
+ raise unittest.case.SkipTest('(no cached-microdesc-consensus)')
elif stem.util.system.is_windows():
- raise stem.util.test_tools.SkipTest('(unavailable on windows)')
+ raise unittest.case.SkipTest('(unavailable on windows)')
count, reported_flags = 0, []
diff --git a/test/integ/descriptor/server_descriptor.py b/test/integ/descriptor/server_descriptor.py
index a62ee8f6..8aa4388c 100644
--- a/test/integ/descriptor/server_descriptor.py
+++ b/test/integ/descriptor/server_descriptor.py
@@ -28,7 +28,7 @@ class TestServerDescriptor(unittest.TestCase):
descriptor_path = os.path.join(test_dir, 'cached-descriptors')
if not os.path.exists(descriptor_path):
- raise stem.util.test_tools.SkipTest('(no cached descriptors)')
+ raise unittest.case.SkipTest('(no cached descriptors)')
with open(descriptor_path, 'rb') as descriptor_file:
for desc in stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0', validate = True):
diff --git a/test/integ/directory/fallback.py b/test/integ/directory/fallback.py
index e12b2659..83c87a8d 100644
--- a/test/integ/directory/fallback.py
+++ b/test/integ/directory/fallback.py
@@ -33,7 +33,6 @@ class TestFallback(unittest.TestCase):
# added so many fallbacks now that this takes a looong time. :(
self.skipTest('(skipped by default)')
- return
unsuccessful = {}
downloader = stem.descriptor.remote.DescriptorDownloader()
diff --git a/test/integ/installation.py b/test/integ/installation.py
index 2ac655aa..2f41ef58 100644
--- a/test/integ/installation.py
+++ b/test/integ/installation.py
@@ -110,9 +110,9 @@ class TestInstallation(unittest.TestCase):
git_dir = os.path.join(test.STEM_BASE, '.git')
if not stem.util.system.is_available('git'):
- raise stem.util.test_tools.SkipTest('(git unavailable)')
+ raise unittest.case.SkipTest('(git unavailable)')
elif not os.path.exists(git_dir):
- raise stem.util.test_tools.SkipTest('(not a git checkout)')
+ raise unittest.case.SkipTest('(not a git checkout)')
if os.path.exists(DIST_PATH):
raise AssertionError("%s already exists, maybe you manually ran 'python setup.py sdist'?" % DIST_PATH)
@@ -127,8 +127,8 @@ class TestInstallation(unittest.TestCase):
# tarball has a prefix 'stem-[verion]' directory so stipping that out
- dist_tar = tarfile.open(os.path.join(DIST_PATH, 'stem-dry-run-%s.tar.gz' % stem.__version__))
- tar_contents = ['/'.join(info.name.split('/')[1:]) for info in dist_tar.getmembers() if info.isfile()]
+ with tarfile.open(os.path.join(DIST_PATH, 'stem-dry-run-%s.tar.gz' % stem.__version__)) as dist_tar:
+ tar_contents = ['/'.join(info.name.split('/')[1:]) for info in dist_tar.getmembers() if info.isfile()]
issues = []
diff --git a/test/integ/interpreter.py b/test/integ/interpreter.py
index 65d03fd3..32c52c28 100644
--- a/test/integ/interpreter.py
+++ b/test/integ/interpreter.py
@@ -38,10 +38,8 @@ class TestInterpreter(unittest.TestCase):
if test.runner.Torrc.PASSWORD in test.runner.get_runner().get_options():
self.skipTest('password auth unsupported')
- return
elif not READLINE_AVAILABLE:
self.skipTest('readline unavailable')
- return
expected = ['250-config-file=%s' % test.runner.get_runner().get_torrc_path(), '250 OK']
self.assertEqual(expected, _run_prompt('--run', 'GETINFO config-file'))
@@ -50,10 +48,8 @@ class TestInterpreter(unittest.TestCase):
def test_running_file(self):
if test.runner.Torrc.PASSWORD in test.runner.get_runner().get_options():
self.skipTest('password auth unsupported')
- return
elif not READLINE_AVAILABLE:
self.skipTest('readline unavailable')
- return
expected = [
'250-config-file=%s' % test.runner.get_runner().get_torrc_path(),
diff --git a/test/integ/manual.py b/test/integ/manual.py
index ae0b2b16..3e721ac6 100644
--- a/test/integ/manual.py
+++ b/test/integ/manual.py
@@ -59,9 +59,6 @@ EXPECTED_EXIT_POLICY_DESCRIPTION_END = 'it applies to both IPv4 and IPv6 address
class TestManual(unittest.TestCase):
- # TODO: remove when dropping support for python 2.6
- skip_reason = 'setUpClass() unsupported in python 2.6'
-
@classmethod
def setUpClass(self):
self.man_path = None
@@ -91,15 +88,14 @@ class TestManual(unittest.TestCase):
if self.man_path and os.path.exists(self.man_path):
os.remove(self.man_path)
+ # TODO: replace with a 'require' annotation
+
def requires_downloaded_manual(self):
if self.skip_reason:
self.skipTest(self.skip_reason)
- return True
elif self.download_error:
self.fail(self.download_error)
- return False
-
def test_escapes_non_ascii(self):
"""
Check that our manual parser escapes all non-ascii characters. If this
@@ -108,8 +104,7 @@ class TestManual(unittest.TestCase):
stem/manual.py's _get_categories().
"""
- if self.requires_downloaded_manual():
- return
+ self.requires_downloaded_manual()
def check(content):
try:
@@ -131,8 +126,7 @@ class TestManual(unittest.TestCase):
it has indented lines within it. Ensure we parse this correctly.
"""
- if self.requires_downloaded_manual():
- return
+ self.requires_downloaded_manual()
manual = stem.manual.Manual.from_man(self.man_path)
self.assertTrue(manual.config_options['ExitPolicy'].description.startswith(EXPECTED_EXIT_POLICY_DESCRIPTION_START))
@@ -143,8 +137,7 @@ class TestManual(unittest.TestCase):
Check if the cached manual information bundled with Stem is up to date or not.
"""
- if self.requires_downloaded_manual():
- return
+ self.requires_downloaded_manual()
cached_manual = stem.manual.Manual.from_cache()
latest_manual = stem.manual.Manual.from_man(self.man_path)
@@ -158,8 +151,7 @@ class TestManual(unittest.TestCase):
then go ahead and simply update these assertions.
"""
- if self.requires_downloaded_manual():
- return
+ self.requires_downloaded_manual()
def assert_equal(category, expected, actual):
if expected != actual:
@@ -208,8 +200,7 @@ class TestManual(unittest.TestCase):
class to match.
"""
- if self.requires_downloaded_manual():
- return
+ self.requires_downloaded_manual()
categories = stem.manual._get_categories(self.man_content)
@@ -230,8 +221,7 @@ class TestManual(unittest.TestCase):
Check that all the configuration options tor supports are in the man page.
"""
- if self.requires_downloaded_manual():
- return
+ self.requires_downloaded_manual()
with test.runner.get_runner().get_tor_controller() as controller:
config_options_in_tor = set([line.split()[0] for line in controller.get_info('config/names').splitlines() if line.split()[1] != 'Virtual'])
diff --git a/test/integ/process.py b/test/integ/process.py
index 26346ece..e40c501a 100644
--- a/test/integ/process.py
+++ b/test/integ/process.py
@@ -9,7 +9,6 @@ import hashlib
import os
import random
import re
-import shutil
import subprocess
import tempfile
import threading
@@ -28,13 +27,9 @@ import test
import test.require
from contextlib import contextmanager
-from stem.util.test_tools import asynchronous, assert_equal, assert_in, skip
+from unittest.mock import patch, Mock
-try:
- # added in python 3.3
- from unittest.mock import patch, Mock
-except ImportError:
- from mock import patch, Mock
+from stem.util.test_tools import asynchronous, assert_equal, assert_in, skip
BASIC_RELAY_TORRC = """\
SocksPort 9089
@@ -57,18 +52,8 @@ def random_port():
@contextmanager
-def tmp_directory():
- tmp_dir = tempfile.mkdtemp()
-
- try:
- yield tmp_dir
- finally:
- shutil.rmtree(tmp_dir)
-
-
-@contextmanager
def torrc():
- with tmp_directory() as data_directory:
+ with tempfile.TemporaryDirectory() as data_directory:
torrc_path = os.path.join(data_directory, 'torrc')
with open(torrc_path, 'w') as torrc_file:
@@ -106,7 +91,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 +162,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'
@@ -236,7 +217,7 @@ class TestProcess(unittest.TestCase):
output = run_tor(tor_cmd, '--list-fingerprint', with_torrc = True, expect_failure = True)
assert_in("Clients don't have long-term identity keys. Exiting.", output, 'Should fail to start due to lacking an ORPort')
- with tmp_directory() as data_directory:
+ with tempfile.TemporaryDirectory() as data_directory:
torrc_path = os.path.join(data_directory, 'torrc')
with open(torrc_path, 'w') as torrc_file:
@@ -332,7 +313,7 @@ class TestProcess(unittest.TestCase):
if test.tor_version() < stem.version.Requirement.TORRC_VIA_STDIN:
skip('(requires %s)' % stem.version.Requirement.TORRC_VIA_STDIN)
- with tmp_directory() as data_directory:
+ with tempfile.TemporaryDirectory() as data_directory:
torrc = BASIC_RELAY_TORRC % data_directory
output = run_tor(tor_cmd, '-f', '-', '--dump-config', 'short', stdin = torrc)
assert_equal(sorted(torrc.splitlines()), sorted(output.splitlines()))
@@ -390,7 +371,7 @@ class TestProcess(unittest.TestCase):
it isn't.
"""
- with tmp_directory() as data_directory:
+ with tempfile.TemporaryDirectory() as data_directory:
# Tries running tor in another thread with the given timeout argument. This
# issues an invalid torrc so we terminate right away if we get to the point
# of actually invoking tor.
@@ -443,7 +424,7 @@ class TestProcess(unittest.TestCase):
Exercises launch_tor_with_config when we write a torrc to disk.
"""
- with tmp_directory() as data_directory:
+ with tempfile.TemporaryDirectory() as data_directory:
control_port = random_port()
control_socket, tor_process = None, None
@@ -487,7 +468,7 @@ class TestProcess(unittest.TestCase):
if test.tor_version() < stem.version.Requirement.TORRC_VIA_STDIN:
skip('(requires %s)' % stem.version.Requirement.TORRC_VIA_STDIN)
- with tmp_directory() as data_directory:
+ with tempfile.TemporaryDirectory() as data_directory:
control_port = random_port()
control_socket, tor_process = None, None
@@ -529,7 +510,7 @@ class TestProcess(unittest.TestCase):
# [warn] Failed to parse/validate config: Failed to bind one of the listener ports.
# [err] Reading config failed--see warnings above.
- with tmp_directory() as data_directory:
+ with tempfile.TemporaryDirectory() as data_directory:
both_ports = random_port()
try:
@@ -551,7 +532,7 @@ class TestProcess(unittest.TestCase):
Runs launch_tor where it times out before completing.
"""
- with tmp_directory() as data_directory:
+ with tempfile.TemporaryDirectory() as data_directory:
start_time = time.time()
try:
@@ -583,7 +564,7 @@ class TestProcess(unittest.TestCase):
elif test.tor_version() < stem.version.Requirement.TAKEOWNERSHIP:
skip('(requires %s)' % stem.version.Requirement.TAKEOWNERSHIP)
- with tmp_directory() as data_directory:
+ with tempfile.TemporaryDirectory() as data_directory:
sleep_process = subprocess.Popen(['sleep', '60'])
tor_process = stem.process.launch_tor_with_config(
@@ -627,7 +608,7 @@ class TestProcess(unittest.TestCase):
if test.tor_version() < stem.version.Requirement.TAKEOWNERSHIP:
skip('(requires %s)' % stem.version.Requirement.TAKEOWNERSHIP)
- with tmp_directory() as data_directory:
+ with tempfile.TemporaryDirectory() as data_directory:
control_port = random_port()
tor_process = stem.process.launch_tor_with_config(
diff --git a/test/integ/response/protocolinfo.py b/test/integ/response/protocolinfo.py
index 5d8c74f6..917b87c3 100644
--- a/test/integ/response/protocolinfo.py
+++ b/test/integ/response/protocolinfo.py
@@ -14,11 +14,7 @@ import test.integ.util.system
import test.require
import test.runner
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
+from unittest.mock import Mock, patch
class TestProtocolInfo(unittest.TestCase):
diff --git a/test/integ/util/connection.py b/test/integ/util/connection.py
index ed55ad89..861b8ba9 100644
--- a/test/integ/util/connection.py
+++ b/test/integ/util/connection.py
@@ -4,6 +4,7 @@ that we're running.
"""
import unittest
+import urllib.request
import stem
import stem.util.connection
@@ -13,12 +14,6 @@ import test.runner
from stem.util.connection import Resolver
-try:
- # account for urllib's change between python 2.x and 3.x
- import urllib.request as urllib
-except ImportError:
- import urllib2 as urllib
-
class TestConnection(unittest.TestCase):
@test.require.ptrace
@@ -27,10 +22,8 @@ class TestConnection(unittest.TestCase):
if test.runner.Torrc.PORT not in runner.get_options():
self.skipTest('(no control port)')
- return
elif resolver not in stem.util.connection.system_resolvers():
self.skipTest('(resolver unavailable on this platform)')
- return
with runner.get_tor_socket():
connections = stem.util.connection.get_connections(resolver, process_pid = runner.get_pid())
@@ -60,7 +53,7 @@ class TestConnection(unittest.TestCase):
self.assertEqual('Failed to download from https://no.such.testing.url (URLError): Name or service not known', str(exc))
self.assertEqual('https://no.such.testing.url', exc.url)
self.assertEqual('Name or service not known', exc.error.reason.strerror)
- self.assertEqual(urllib.URLError, type(exc.error))
+ self.assertEqual(urllib.request.URLError, type(exc.error))
def test_connections_by_proc(self):
self.check_resolver(Resolver.PROC)
diff --git a/test/integ/util/proc.py b/test/integ/util/proc.py
index da4d6efc..315082d5 100644
--- a/test/integ/util/proc.py
+++ b/test/integ/util/proc.py
@@ -76,7 +76,6 @@ class TestProc(unittest.TestCase):
return
elif not os.access('/proc/net/tcp', os.R_OK) or not os.access('/proc/net/udp', os.R_OK):
self.skipTest('(proc lacks read permissions)')
- return
# making a controller connection so that we have something to query for
with runner.get_tor_socket():
diff --git a/test/integ/util/system.py b/test/integ/util/system.py
index 89019685..2a7ac07a 100644
--- a/test/integ/util/system.py
+++ b/test/integ/util/system.py
@@ -14,13 +14,9 @@ import stem.util.system
import test.require
import test.runner
-from stem.util.system import State, DaemonTask
+from unittest.mock import Mock, patch
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
+from stem.util.system import State, DaemonTask
def filter_system_call(prefixes):
@@ -287,15 +283,12 @@ class TestSystem(unittest.TestCase):
if stem.util.system.is_windows():
self.skipTest('(unavailable on windows)')
- return
elif stem.util.system.is_mac() or stem.util.system.is_gentoo():
self.skipTest('(resolvers unavailable)')
- return
elif not stem.util.system.is_available('netstat') or \
stem.util.system.is_available('sockstat') or \
stem.util.system.is_available('lsof'):
self.skipTest('(connection resolvers unavailable)')
- return
runner = test.runner.get_runner()
tor_pid, tor_port = runner.get_pid(), test.runner.CONTROL_PORT
@@ -313,7 +306,6 @@ class TestSystem(unittest.TestCase):
if stem.util.system.is_gentoo():
self.skipTest('(unavailable on gentoo)')
- return
netstat_prefix = stem.util.system.GET_PID_BY_PORT_NETSTAT
@@ -353,7 +345,6 @@ class TestSystem(unittest.TestCase):
if stem.util.system.is_mac() or stem.util.system.is_gentoo():
self.skipTest('(resolvers unavailable)')
- return
lsof_prefix = stem.util.system.GET_PID_BY_PORT_LSOF
@@ -370,13 +361,14 @@ class TestSystem(unittest.TestCase):
Checks the stem.util.system.pid_by_open_file function.
"""
+ # check a directory that doesn't exist
+
+ self.assertEqual(None, stem.util.system.pid_by_open_file('/no/such/path'))
+
# check a directory that exists, but isn't claimed by any application
- tmpdir = tempfile.mkdtemp()
- self.assertEqual(None, stem.util.system.pid_by_open_file(tmpdir))
- # check a directory that doesn't exist
- os.rmdir(tmpdir)
- self.assertEqual(None, stem.util.system.pid_by_open_file(tmpdir))
+ with tempfile.TemporaryDirectory() as tmpdir:
+ self.assertEqual(None, stem.util.system.pid_by_open_file(tmpdir))
@require_path
def test_pids_by_user(self):
@@ -397,7 +389,6 @@ class TestSystem(unittest.TestCase):
if stem.util.system.is_windows():
self.skipTest('(unavailable on windows)')
- return
runner = test.runner.get_runner()
runner_pid, tor_cwd = runner.get_pid(), runner.get_tor_cwd()
@@ -537,7 +528,6 @@ class TestSystem(unittest.TestCase):
if getpass.getuser() == 'root':
self.skipTest('(running as root)')
- return
self.assertEqual(os.getcwd(), stem.util.system.expand_path('.'))
self.assertEqual(os.getcwd(), stem.util.system.expand_path('./'))
@@ -568,7 +558,6 @@ class TestSystem(unittest.TestCase):
if stem.prereq.is_pypy():
self.skipTest('(unimplemented for pypy)')
- return
initial_name = stem.util.system.get_process_name()
self.assertTrue('run_tests.py' in initial_name)
diff --git a/test/network.py b/test/network.py
index 5072c5b6..5fea625e 100644
--- a/test/network.py
+++ b/test/network.py
@@ -127,9 +127,7 @@ class Socks(_socket_socket):
:param int expected_size: number of bytes to return
- :returns:
- * **str** in Python 2 (bytes is str)
- * **bytes** in Python 3
+ :returns: **bytes** for the requested content
:raises:
* :class:`socket.error` for socket errors
@@ -152,32 +150,21 @@ class Socks(_socket_socket):
:param list integers: list of ints to convert
- :returns:
- * **str** in Python 2 (bytes is str)
- * **bytes** in Python 3
+ :returns: **bytes** for the requested content
"""
- if bytes is str:
- bytes_ = ''.join([chr(x) for x in integers]) # Python 2
- else:
- bytes_ = bytes(integers) # Python 3
- return bytes_
+ return bytes(integers)
def _bytes_to_ints(self, bytes_):
"""
- Returns a tuple of integers converted from a string (Python 2) or
- bytes (Python 3).
+ Returns a tuple of integers converted from bytes.
- :param str,bytes bytes_: byte string to convert
+ :param bytes bytes_: byte string to convert
:returns: **list** of ints
"""
- try:
- integers = [ord(x) for x in bytes_] # Python 2
- except TypeError:
- integers = [x for x in bytes_] # Python 3
- return tuple(integers)
+ return tuple([x for x in bytes_])
def _pack_string(self, string_):
"""
@@ -185,16 +172,10 @@ class Socks(_socket_socket):
:param str string_: string to convert
- :returns:
- * **str** in Python 2 (bytes is str)
- * **bytes** in Python 3
+ :returns: **bytes** for the requested content
"""
- try:
- return struct.pack('>%ss' % len(string_), string_)
- except struct.error:
- # Python 3: encode str to bytes
- return struct.pack('>%ss' % len(string_), string_.encode())
+ return struct.pack('>%ss' % len(string_), string_.encode())
def connect(self, address):
"""
diff --git a/test/task.py b/test/task.py
index 1fbcf9a4..19026165 100644
--- a/test/task.py
+++ b/test/task.py
@@ -14,7 +14,6 @@
|- PYTHON_VERSION - checks our python version
|- PLATFORM_VERSION - checks our operating system version
|- CRYPTO_VERSION - checks our version of cryptography
- |- MOCK_VERSION - checks our version of mock
|- PYFLAKES_VERSION - checks our version of pyflakes
|- PYCODESTYLE_VERSION - checks our version of pycodestyle
|- CLEAN_PYC - removes any *.pyc without a corresponding *.py
@@ -25,6 +24,7 @@
+- PYCODESTYLE_TASK - style checks
"""
+import importlib
import os
import platform
import re
@@ -46,14 +46,6 @@ from test.output import STATUS, ERROR, NO_NL, println
TASK_DESCRIPTION_WIDTH = 40
-try:
- # TODO: remove check when dropping python 2.6 support
-
- import importlib
- HAS_IMPORTLIB = True
-except ImportError:
- HAS_IMPORTLIB = False
-
CONFIG = stem.util.conf.config_dict('test', {
'integ.test_directory': './test/data',
'test.unit_tests': '',
@@ -163,9 +155,6 @@ def _import_tests():
register if they're asynchronous.
"""
- if not HAS_IMPORTLIB:
- return
-
for module in (CONFIG['test.unit_tests'].splitlines() + CONFIG['test.integ_tests'].splitlines()):
try:
importlib.import_module(module.rsplit('.', 1)[0])
@@ -273,7 +262,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)
@@ -305,7 +294,7 @@ class ModuleVersion(Task):
def version_check():
if prereq_check is None or prereq_check():
for module in modules:
- if HAS_IMPORTLIB and stem.util.test_tools._module_exists(module):
+ if stem.util.test_tools._module_exists(module):
return importlib.import_module(module).__version__
return 'missing'
@@ -333,7 +322,6 @@ TOR_VERSION = Task('tor version', _check_tor_version)
PYTHON_VERSION = Task('python version', _check_python_version)
PLATFORM_VERSION = Task('operating system', _check_platform_version)
CRYPTO_VERSION = ModuleVersion('cryptography version', 'cryptography', stem.prereq.is_crypto_available)
-MOCK_VERSION = ModuleVersion('mock version', ['unittest.mock', 'mock'], stem.prereq.is_mock_available)
PYFLAKES_VERSION = ModuleVersion('pyflakes version', 'pyflakes')
PYCODESTYLE_VERSION = ModuleVersion('pycodestyle version', ['pycodestyle', 'pep8'])
CLEAN_PYC = Task('checking for orphaned .pyc files', _clean_orphaned_pyc, (SRC_PATHS,), print_runtime = True)
diff --git a/test/unit/client/size.py b/test/unit/client/size.py
index dcd5fea3..d3bb4a4a 100644
--- a/test/unit/client/size.py
+++ b/test/unit/client/size.py
@@ -11,8 +11,6 @@ from stem.client.datatype import Size
class TestSize(unittest.TestCase):
def test_attributes(self):
self.assertEqual('CHAR', Size.CHAR.name)
- self.assertEqual('!B', Size.CHAR.format)
-
self.assertEqual(1, Size.CHAR.size)
self.assertEqual(2, Size.SHORT.size)
self.assertEqual(4, Size.LONG.size)
@@ -27,8 +25,8 @@ class TestSize(unittest.TestCase):
self.assertRaisesWith(ValueError, 'Size.pack encodes an integer, but was a str', Size.CHAR.pack, 'hi')
self.assertRaisesWith(ValueError, 'Packed values must be positive (attempted to pack -1 as a CHAR)', Size.CHAR.pack, -1)
- bad_size = Size('BAD_SIZE', 1, '!H')
- self.assertRaisesRegexp(ValueError, re.escape("'\\x00\\x12' is the wrong size for a BAD_SIZE field"), bad_size.pack, 18)
+ too_small = Size('TOO_SMALL', 1)
+ self.assertRaises(OverflowError, too_small.pack, 1800)
def test_unpack(self):
self.assertEqual(18, Size.CHAR.unpack(b'\x12'))
diff --git a/test/unit/connection/authentication.py b/test/unit/connection/authentication.py
index 117b39d9..f6241e0e 100644
--- a/test/unit/connection/authentication.py
+++ b/test/unit/connection/authentication.py
@@ -14,15 +14,11 @@ import unittest
import stem.connection
import test
+from unittest.mock import Mock, patch
+
from stem.response import ControlMessage
from stem.util import log
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
-
class TestAuthenticate(unittest.TestCase):
@patch('stem.connection.get_protocolinfo')
diff --git a/test/unit/connection/connect.py b/test/unit/connection/connect.py
index 37ded2f5..175a1ebd 100644
--- a/test/unit/connection/connect.py
+++ b/test/unit/connection/connect.py
@@ -2,25 +2,18 @@
Unit tests for the stem.connection.connect function.
"""
+import io
import unittest
-try:
- from StringIO import StringIO
-except ImportError:
- from io import StringIO
-
-try:
- from mock import Mock, patch
-except ImportError:
- from unittest.mock import Mock, patch
-
import stem
import stem.connection
import stem.socket
+from unittest.mock import Mock, patch
+
class TestConnect(unittest.TestCase):
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
@patch('stem.util.system.is_running')
@patch('os.path.exists', Mock(return_value = True))
@patch('stem.socket.ControlSocketFile', Mock(side_effect = stem.SocketError('failed')))
@@ -33,7 +26,7 @@ class TestConnect(unittest.TestCase):
is_running_mock.return_value = True
self._assert_connect_fails_with({}, stdout_mock, "Unable to connect to tor. Maybe it's running without a ControlPort?")
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
@patch('os.path.exists')
@patch('stem.util.system.is_running', Mock(return_value = True))
@patch('stem.socket.ControlSocketFile', Mock(side_effect = stem.SocketError('failed')))
@@ -121,7 +114,7 @@ class TestConnect(unittest.TestCase):
authenticate_mock.assert_any_call(control_socket, None, None)
authenticate_mock.assert_any_call(control_socket, 'my_password', None)
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
@patch('stem.connection.authenticate')
def test_auth_failure(self, authenticate_mock, stdout_mock):
control_socket = stem.socket.ControlPort(connect = False)
diff --git a/test/unit/control/controller.py b/test/unit/control/controller.py
index 94c1b65f..9628c913 100644
--- a/test/unit/control/controller.py
+++ b/test/unit/control/controller.py
@@ -14,17 +14,13 @@ import stem.socket
import stem.util.system
import stem.version
+from unittest.mock import Mock, patch
+
from stem import ControllerError, DescriptorUnavailable, InvalidArguments, InvalidRequest, ProtocolError, UnsatisfiableRequest
from stem.control import MALFORMED_EVENTS, _parse_circ_path, Listener, Controller, EventType
from stem.response import ControlMessage
from stem.exit_policy import ExitPolicy
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
-
NS_DESC = 'r %s %s u5lTXJKGsLKufRLnSyVqT7TdGYw 2012-12-30 22:02:49 77.223.43.54 9001 0\ns Fast Named Running Stable Valid\nw Bandwidth=75'
TEST_TIMESTAMP = 12345
diff --git a/test/unit/descriptor/bandwidth_file.py b/test/unit/descriptor/bandwidth_file.py
index 3040c38b..9bee5f95 100644
--- a/test/unit/descriptor/bandwidth_file.py
+++ b/test/unit/descriptor/bandwidth_file.py
@@ -2,26 +2,17 @@
Unit tests for stem.descriptor.bandwidth_file.
"""
+import collections
import datetime
import unittest
import stem.descriptor
+from unittest.mock import Mock, patch
+
from stem.descriptor.bandwidth_file import BandwidthFile
from test.unit.descriptor import get_resource
-try:
- # added in python 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
-
EXPECTED_MEASUREMENT_1 = {
'scanner': '/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22',
'measured_at': '1547441722',
@@ -272,7 +263,7 @@ class TestBandwidthFile(unittest.TestCase):
Exercise the example in our content method's pydoc.
"""
- content = BandwidthFile.content(OrderedDict([
+ content = BandwidthFile.content(collections.OrderedDict([
('timestamp', '12345'),
('version', '1.2.0'),
('content', []),
@@ -286,7 +277,7 @@ class TestBandwidthFile(unittest.TestCase):
Include an unrecognized header field.
"""
- desc = BandwidthFile.create(OrderedDict([('version', '1.1.0'), ('new_header', 'neat stuff')]))
+ desc = BandwidthFile.create(collections.OrderedDict([('version', '1.1.0'), ('new_header', 'neat stuff')]))
self.assertEqual(EXPECTED_NEW_HEADER_CONTENT, str(desc))
self.assertEqual('1.1.0', desc.version)
self.assertEqual({'version': '1.1.0', 'new_header': 'neat stuff'}, desc.header)
@@ -309,7 +300,7 @@ class TestBandwidthFile(unittest.TestCase):
self.assertRaisesWith(ValueError, "The 'version' header must be in the second position", BandwidthFile.from_str, WRONG_VERSION_POSITION, validate = True)
- content = BandwidthFile.content(OrderedDict([
+ content = BandwidthFile.content(collections.OrderedDict([
('timestamp', '1410723598'),
('file_created', '2019-01-14T05:35:06'),
('version', '1.1.0'),
diff --git a/test/unit/descriptor/collector.py b/test/unit/descriptor/collector.py
index b4ff7636..99e19d7c 100644
--- a/test/unit/descriptor/collector.py
+++ b/test/unit/descriptor/collector.py
@@ -8,20 +8,13 @@ import unittest
import stem.prereq
+from unittest.mock import Mock, patch
+
from stem.descriptor import Compression, DocumentHandler
from stem.descriptor.collector import CollecTor, File
from test.unit.descriptor import get_resource
from test.unit.descriptor.data.collector.index import EXAMPLE_INDEX
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-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 +53,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,11 +61,10 @@ 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)')
- return
import zlib
urlopen_mock.return_value = io.BytesIO(zlib.compress(EXAMPLE_INDEX_JSON))
@@ -81,11 +73,10 @@ 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)')
- return
import bz2
urlopen_mock.return_value = io.BytesIO(bz2.compress(EXAMPLE_INDEX_JSON))
@@ -94,11 +85,10 @@ 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)')
- return
import lzma
urlopen_mock.return_value = io.BytesIO(lzma.compress(EXAMPLE_INDEX_JSON))
@@ -107,7 +97,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')
@@ -121,21 +111,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/compression.py b/test/unit/descriptor/compression.py
index 3945bc9c..a51bba62 100644
--- a/test/unit/descriptor/compression.py
+++ b/test/unit/descriptor/compression.py
@@ -32,7 +32,6 @@ class TestCompression(unittest.TestCase):
if not compression.available:
self.skipTest('(%s unavailable)' % compression)
- return
with open(get_resource(filename), 'rb') as compressed_file:
content = compression.decompress(compressed_file.read())
diff --git a/test/unit/descriptor/export.py b/test/unit/descriptor/export.py
index a33c1d42..d27ed241 100644
--- a/test/unit/descriptor/export.py
+++ b/test/unit/descriptor/export.py
@@ -2,13 +2,9 @@
Unit tests for stem.descriptor.export.
"""
+import io
import unittest
-try:
- from StringIO import StringIO
-except ImportError:
- from io import StringIO
-
import stem.prereq
from stem.descriptor.server_descriptor import RelayDescriptor, BridgeDescriptor
@@ -21,10 +17,6 @@ class TestExport(unittest.TestCase):
Exports a single minimal tor server descriptor.
"""
- if stem.prereq._is_python_26():
- self.skipTest('(header added in python 2.7)')
- return
-
desc = RelayDescriptor.create({
'router': 'caerSidi 71.35.133.197 9001 0 0',
'published': '2012-03-01 17:15:27',
@@ -63,7 +55,7 @@ class TestExport(unittest.TestCase):
desc = RelayDescriptor.create()
desc_csv = export_csv(desc)
- csv_buffer = StringIO()
+ csv_buffer = io.StringIO()
export_csv_file(csv_buffer, desc)
self.assertEqual(desc_csv, csv_buffer.getvalue())
@@ -73,10 +65,6 @@ class TestExport(unittest.TestCase):
Checks that the default attributes for our csv output doesn't include private fields.
"""
- if stem.prereq._is_python_26():
- self.skipTest('(header added in python 2.7)')
- return
-
desc = RelayDescriptor.create()
desc_csv = export_csv(desc)
diff --git a/test/unit/descriptor/hidden_service_v3.py b/test/unit/descriptor/hidden_service_v3.py
index 33507314..4004a94d 100644
--- a/test/unit/descriptor/hidden_service_v3.py
+++ b/test/unit/descriptor/hidden_service_v3.py
@@ -3,6 +3,7 @@ Unit tests for stem.descriptor.hidden_service for version 3.
"""
import base64
+import collections
import functools
import unittest
@@ -13,6 +14,8 @@ import stem.prereq
import test.require
+from unittest.mock import patch, Mock
+
from stem.descriptor.hidden_service import (
IntroductionPointV3,
HiddenServiceDescriptorV3,
@@ -27,18 +30,6 @@ from test.unit.descriptor import (
base_expect_invalid_attr_for_text,
)
-try:
- # added in python 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
-try:
- # added in python 3.3
- from unittest.mock import patch, Mock
-except ImportError:
- from mock import patch, Mock
-
require_sha3 = test.require.needs(stem.prereq._is_sha3_available, 'requires sha3')
require_x25519 = test.require.needs(lambda: stem.descriptor.hidden_service.X25519_AVAILABLE, 'requires openssl x5509')
@@ -328,7 +319,7 @@ class TestHiddenServiceDescriptorV3(unittest.TestCase):
# include optional parameters
- desc = InnerLayer.create(OrderedDict((
+ desc = InnerLayer.create(collections.OrderedDict((
('intro-auth-required', 'ed25519'),
('single-onion-service', ''),
)))
diff --git a/test/unit/descriptor/networkstatus/document_v3.py b/test/unit/descriptor/networkstatus/document_v3.py
index ce7cfdb7..93f98d77 100644
--- a/test/unit/descriptor/networkstatus/document_v3.py
+++ b/test/unit/descriptor/networkstatus/document_v3.py
@@ -2,6 +2,7 @@
Unit tests for the NetworkStatusDocumentV3 of stem.descriptor.networkstatus.
"""
+import collections
import datetime
import io
import unittest
@@ -31,12 +32,6 @@ from stem.descriptor.router_status_entry import (
from test.unit.descriptor import get_resource
-try:
- # added in python 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
BANDWIDTH_WEIGHT_ENTRIES = (
'Wbd', 'Wbe', 'Wbg', 'Wbm',
'Wdb',
@@ -869,7 +864,7 @@ DnN5aFtYKiTc19qIC7Nmo+afPdDEf0MlJvEOP5EWl3w=
Parses the parameters attributes.
"""
- document = NetworkStatusDocumentV3.create(OrderedDict([
+ document = NetworkStatusDocumentV3.create(collections.OrderedDict([
('vote-status', 'vote'),
('recommended-client-protocols', 'HSDir=1 HSIntro=3'),
('recommended-relay-protocols', 'Cons=1 Desc=1'),
@@ -1231,7 +1226,7 @@ DnN5aFtYKiTc19qIC7Nmo+afPdDEf0MlJvEOP5EWl3w=
COMMITMENT_1 = '1 sha3-256 4CAEC248004A0DC6CE86EBD5F608C9B05500C70C AAAAAFd4/kAaklgYr4ijHZjXXy/B354jQfL31BFhhE46nuOHSPITyw== AAAAAFd4/kCpZeis3yJyr//rz8hXCeeAhHa4k3lAcAiMJd1vEMTPuw=='
COMMITMENT_2 = '1 sha3-256 598536A9DD4E6C0F18B4AD4B88C7875A0A29BA31 AAAAAFd4/kC7S920awC5/HF5RfX4fKZtYqjm6qMh9G91AcjZm13DQQ=='
- authority = DirectoryAuthority.create(OrderedDict([
+ authority = DirectoryAuthority.create(collections.OrderedDict([
('shared-rand-participate', ''),
('shared-rand-commit', '%s\nshared-rand-commit %s' % (COMMITMENT_1, COMMITMENT_2)),
('shared-rand-previous-value', '8 hAQLxyt0U3gu7QR2owixRCbIltcyPrz3B0YBfUshOkE='),
diff --git a/test/unit/descriptor/reader.py b/test/unit/descriptor/reader.py
index 76d1d8f3..f49183e5 100644
--- a/test/unit/descriptor/reader.py
+++ b/test/unit/descriptor/reader.py
@@ -19,11 +19,7 @@ import stem.util.system
import test.unit.descriptor
-try:
- # added in python 3.3
- from unittest.mock import patch
-except ImportError:
- from mock import patch
+from unittest.mock import patch
BASIC_LISTING = """
/tmp 123
@@ -44,23 +40,13 @@ def _get_raw_tar_descriptors():
test_path = os.path.join(DESCRIPTOR_TEST_DATA, 'descriptor_archive.tar')
raw_descriptors = []
- # TODO: revert to using the 'with' keyword for this when dropping python
- # 2.6 support
-
- tar_file = None
-
- try:
- tar_file = tarfile.open(test_path)
-
+ with tarfile.open(test_path) as tar_file:
for tar_entry in tar_file:
if tar_entry.isfile():
entry = tar_file.extractfile(tar_entry)
entry.readline() # strip header
raw_descriptors.append(entry.read().decode('utf-8', 'replace'))
entry.close()
- finally:
- if tar_file:
- tar_file.close()
TAR_DESCRIPTORS = raw_descriptors
@@ -192,7 +178,6 @@ class TestDescriptorReader(unittest.TestCase):
if getpass.getuser() == 'root':
self.skipTest('(running as root)')
- return
# Skip the test on windows, since you can only set the file's
# read-only flag with os.chmod(). For more information see...
@@ -558,10 +543,8 @@ class TestDescriptorReader(unittest.TestCase):
if getpass.getuser() == 'root':
self.skipTest('(running as root)')
- return
elif stem.util.system.is_windows():
self.skipTest('(chmod not functional)')
- return
test_path = os.path.join(self.temp_directory, 'secret_file')
diff --git a/test/unit/descriptor/remote.py b/test/unit/descriptor/remote.py
index ab0ad3d7..d9893535 100644
--- a/test/unit/descriptor/remote.py
+++ b/test/unit/descriptor/remote.py
@@ -2,6 +2,7 @@
Unit tests for stem.descriptor.remote.
"""
+import http.client
import io
import socket
import time
@@ -13,26 +14,11 @@ import stem.descriptor.remote
import stem.prereq
import stem.util.str_tools
+from unittest.mock import patch, Mock, MagicMock
+
from stem.descriptor.remote import Compression
from test.unit.descriptor import read_resource
-try:
- from http.client import HTTPMessage # python3
-except ImportError:
- from httplib import HTTPMessage # python2
-
-try:
- # added in python 3.3
- from unittest.mock import patch, Mock, MagicMock
-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 +90,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 = http.client.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 +148,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 +186,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 +201,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 +216,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.
@@ -241,7 +224,6 @@ class TestDescriptorDownloader(unittest.TestCase):
if not stem.prereq.is_zstd_available():
self.skipTest('(requires zstd module)')
- return
descriptors = list(stem.descriptor.remote.get_server_descriptors(
'9695DFC35FFEB861329B9F1AB04C46397020CE31',
@@ -252,7 +234,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.
@@ -260,7 +242,6 @@ class TestDescriptorDownloader(unittest.TestCase):
if not stem.prereq.is_lzma_available():
self.skipTest('(requires lzma module)')
- return
descriptors = list(stem.descriptor.remote.get_server_descriptors(
'9695DFC35FFEB861329B9F1AB04C46397020CE31',
@@ -271,7 +252,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.
@@ -288,7 +269,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
@@ -311,7 +292,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.
@@ -336,7 +317,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.
@@ -363,7 +344,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)
@@ -398,7 +379,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/descriptor/router_status_entry.py b/test/unit/descriptor/router_status_entry.py
index c428d6b2..7ee942e0 100644
--- a/test/unit/descriptor/router_status_entry.py
+++ b/test/unit/descriptor/router_status_entry.py
@@ -2,6 +2,7 @@
Unit tests for stem.descriptor.router_status_entry.
"""
+import collections
import datetime
import functools
import unittest
@@ -27,12 +28,6 @@ from stem.descriptor.router_status_entry import (
_base64_to_hex,
)
-try:
- # Added in 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
ENTRY_WITHOUT_ED25519 = """\
r seele AAoQ1DAR6kkoo19hBAX5K0QztNw m0ynPuwzSextzsiXYJYA0Hce+Cs 2015-08-23 00:26:35 73.15.150.172 9001 0
s Running Stable Valid
@@ -258,7 +253,7 @@ class TestRouterStatusEntry(unittest.TestCase):
Parse a router status entry with an IPv6 address.
"""
- expected_protocols = OrderedDict((
+ expected_protocols = collections.OrderedDict((
('Cons', [1]),
('Desc', [1]),
('DirCache', [1]),
diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py
index d87d51e9..aeb58df1 100644
--- a/test/unit/descriptor/server_descriptor.py
+++ b/test/unit/descriptor/server_descriptor.py
@@ -2,6 +2,7 @@
Unit tests for stem.descriptor.server_descriptor.
"""
+import collections
import datetime
import functools
import hashlib
@@ -20,6 +21,8 @@ import stem.version
import stem.util.str_tools
import test.require
+from unittest.mock import Mock, patch
+
from stem.client.datatype import CertType
from stem.descriptor import DigestHash, DigestEncoding
from stem.descriptor.certificate import ExtensionType
@@ -31,18 +34,6 @@ from test.unit.descriptor import (
base_expect_invalid_attr_for_text,
)
-try:
- # Added in 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
-
TARFILE_FINGERPRINTS = set([
'B6D83EC2D9E18B0A7A33428F8CFA9C536769E209',
'E0BD57A11F00041A9789577C53A1B784473669E4',
@@ -74,16 +65,12 @@ class TestServerDescriptor(unittest.TestCase):
Fetch server descriptors via parse_file() for a tarfile object.
"""
- # TODO: When dropping python 2.6 support we can go back to using the 'with'
- # keyword here.
+ with tarfile.open(get_resource('descriptor_archive.tar')) as tar_file:
+ descriptors = list(stem.descriptor.parse_file(tar_file))
+ self.assertEqual(3, len(descriptors))
- tar_file = tarfile.open(get_resource('descriptor_archive.tar'))
- descriptors = list(stem.descriptor.parse_file(tar_file))
- self.assertEqual(3, len(descriptors))
-
- fingerprints = set([desc.fingerprint for desc in descriptors])
- self.assertEqual(TARFILE_FINGERPRINTS, fingerprints)
- tar_file.close()
+ fingerprints = set([desc.fingerprint for desc in descriptors])
+ self.assertEqual(TARFILE_FINGERPRINTS, fingerprints)
def test_metrics_descriptor(self):
"""
@@ -288,7 +275,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
exc_msg = 'Server descriptor lacks a fingerprint. This is an optional field, but required to make a router status entry.'
self.assertRaisesWith(ValueError, exc_msg, desc_without_fingerprint.make_router_status_entry)
- desc = RelayDescriptor.create(OrderedDict((
+ desc = RelayDescriptor.create(collections.OrderedDict((
('router', 'caerSidi 71.35.133.197 9001 0 0'),
('published', '2012-02-29 04:03:19'),
('fingerprint', '4F0C 867D F0EF 6816 0568 C826 838F 482C EA7C FE44'),
diff --git a/test/unit/directory/authority.py b/test/unit/directory/authority.py
index 1c2a86b4..1574bea3 100644
--- a/test/unit/directory/authority.py
+++ b/test/unit/directory/authority.py
@@ -9,13 +9,7 @@ import stem
import stem.directory
import stem.prereq
-try:
- # added in python 3.3
- from unittest.mock import patch, Mock
-except ImportError:
- from mock import patch, Mock
-
-URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
+from unittest.mock import patch, Mock
AUTHORITY_GITWEB_CONTENT = b"""\
"moria1 orport=9101 "
@@ -53,7 +47,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 +71,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 06b4510d..a7c54efb 100644
--- a/test/unit/directory/fallback.py
+++ b/test/unit/directory/fallback.py
@@ -2,6 +2,7 @@
Unit tests for stem.directory.Fallback.
"""
+import collections
import io
import re
import tempfile
@@ -11,19 +12,7 @@ import stem
import stem.directory
import stem.util.conf
-try:
- # added in python 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
-try:
- # added in python 3.3
- from unittest.mock import patch, Mock
-except ImportError:
- from mock import patch, Mock
-
-URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
+from unittest.mock import patch, Mock
FALLBACK_GITWEB_CONTENT = b"""\
/* type=fallback */
@@ -58,7 +47,7 @@ URL: https:onionoo.torproject.orguptime?first_seen_days=30-&flag=V2Dir&type=rela
/* ===== */
"""
-HEADER = OrderedDict((
+HEADER = collections.OrderedDict((
('type', 'fallback'),
('version', '2.0.0'),
('timestamp', '20170526090242'),
@@ -75,7 +64,7 @@ class TestFallback(unittest.TestCase):
'nickname': 'rueckgrat',
'has_extrainfo': True,
'orport_v6': ('2a01:4f8:162:51e2::2', 9001),
- 'header': OrderedDict((
+ 'header': collections.OrderedDict((
('type', 'fallback'),
('version', '2.0.0'),
('timestamp', '20170526090242'),
@@ -95,7 +84,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(
@@ -122,15 +111,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)
@@ -145,7 +134,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/doctest.py b/test/unit/doctest.py
index 28eef0cf..84712dc2 100644
--- a/test/unit/doctest.py
+++ b/test/unit/doctest.py
@@ -15,13 +15,9 @@ import stem.util.system
import stem.version
import test
-from stem.response import ControlMessage
+from unittest.mock import Mock, patch
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
+from stem.response import ControlMessage
EXPECTED_CIRCUIT_STATUS = """\
20 EXTENDED $718BCEA286B531757ACAFF93AE04910EA73DE617=KsmoinOK,$649F2D0ACF418F7CFC6539AB2257EB2D5297BAFA=Eskimo BUILD_FLAGS=NEED_CAPACITY PURPOSE=GENERAL TIME_CREATED=2012-12-06T13:51:11.433755
diff --git a/test/unit/exit_policy/policy.py b/test/unit/exit_policy/policy.py
index f6cf9957..0cb755ae 100644
--- a/test/unit/exit_policy/policy.py
+++ b/test/unit/exit_policy/policy.py
@@ -5,11 +5,7 @@ Unit tests for the stem.exit_policy.ExitPolicy class.
import pickle
import unittest
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
+from unittest.mock import Mock, patch
from stem.exit_policy import (
DEFAULT_POLICY_RULES,
diff --git a/test/unit/installation.py b/test/unit/installation.py
index fd8709ba..4b2a795c 100644
--- a/test/unit/installation.py
+++ b/test/unit/installation.py
@@ -7,9 +7,6 @@ import test
class TestInstallation(unittest.TestCase):
- # TODO: remove when dropping support for python 2.6
- skip_reason = 'setUpClass() unsupported in python 2.6'
-
@classmethod
def setUpClass(self):
setup_path = os.path.join(test.STEM_BASE, 'setup.py')
@@ -25,7 +22,6 @@ class TestInstallation(unittest.TestCase):
def test_installs_all_modules(self):
if self.skip_reason:
self.skipTest(self.skip_reason)
- return True
# Modules cited my our setup.py looks like...
#
@@ -49,7 +45,6 @@ class TestInstallation(unittest.TestCase):
def test_installs_all_data_files(self):
if self.skip_reason:
self.skipTest(self.skip_reason)
- return True
# Checking that we have all non-source files. Data looks like...
#
diff --git a/test/unit/interpreter/__init__.py b/test/unit/interpreter/__init__.py
index 7c107687..a48a19bc 100644
--- a/test/unit/interpreter/__init__.py
+++ b/test/unit/interpreter/__init__.py
@@ -9,11 +9,7 @@ __all__ = [
'help',
]
-try:
- # added in python 3.3
- from unittest.mock import Mock
-except ImportError:
- from mock import Mock
+from unittest.mock import Mock
GETINFO_NAMES = """
info/names -- List of GETINFO options, types, and documentation.
diff --git a/test/unit/interpreter/autocomplete.py b/test/unit/interpreter/autocomplete.py
index 40bcab48..8971ddc7 100644
--- a/test/unit/interpreter/autocomplete.py
+++ b/test/unit/interpreter/autocomplete.py
@@ -1,15 +1,10 @@
import unittest
-from stem.interpreter.autocomplete import _get_commands, Autocompleter
+from unittest.mock import Mock
+from stem.interpreter.autocomplete import _get_commands, Autocompleter
from test.unit.interpreter import CONTROLLER
-try:
- # added in python 3.3
- from unittest.mock import Mock
-except ImportError:
- from mock import Mock
-
class TestAutocompletion(unittest.TestCase):
def test_autocomplete_results_from_config(self):
diff --git a/test/unit/interpreter/commands.py b/test/unit/interpreter/commands.py
index 59ceadfe..412178e6 100644
--- a/test/unit/interpreter/commands.py
+++ b/test/unit/interpreter/commands.py
@@ -5,16 +5,12 @@ import stem
import stem.response
import stem.version
+from unittest.mock import Mock, patch
+
from stem.interpreter.commands import ControlInterpreter, _get_fingerprint
from stem.response import ControlMessage
from test.unit.interpreter import CONTROLLER
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
-
EXPECTED_EVENTS_RESPONSE = """\
\x1b[34mBW 15 25\x1b[0m
\x1b[34mBW 758 570\x1b[0m
diff --git a/test/unit/manual.py b/test/unit/manual.py
index 72f847e5..e1891ca4 100644
--- a/test/unit/manual.py
+++ b/test/unit/manual.py
@@ -2,36 +2,21 @@
Unit testing for the stem.manual module.
"""
+import collections
import io
import os
import sqlite3
import tempfile
import unittest
+import urllib.request
import stem.prereq
import stem.manual
import stem.util.system
import test.require
-try:
- # account for urllib's change between python 2.x and 3.x
- import urllib.request as urllib
-except ImportError:
- import urllib2 as urllib
-
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
-
-try:
- # added in python 2.7
- from collections import OrderedDict
-except ImportError:
- from stem.util.ordereddict import OrderedDict
-
-URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
+from unittest.mock import Mock, patch
+
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')
@@ -59,7 +44,7 @@ EXPECTED_FILES = {
'$HOME/.torrc': 'Fallback location for torrc, if @CONFDIR@/torrc is not found.',
}
-EXPECTED_CONFIG_OPTIONS = OrderedDict()
+EXPECTED_CONFIG_OPTIONS = collections.OrderedDict()
EXPECTED_CONFIG_OPTIONS['BandwidthRate'] = stem.manual.ConfigOption(
name = 'BandwidthRate',
@@ -91,6 +76,10 @@ EXPECTED_CONFIG_OPTIONS['Bridge'] = stem.manual.ConfigOption(
CACHED_MANUAL = None
+TEMP_DIR_MOCK = Mock()
+TEMP_DIR_MOCK.__enter__ = Mock(return_value = '/no/such/path')
+TEMP_DIR_MOCK.__exit__ = Mock(return_value = False)
+
def _cached_manual():
global CACHED_MANUAL
@@ -154,7 +143,6 @@ class TestManual(unittest.TestCase):
if not stem.manual.HAS_ENCODING_ARG:
self.skipTest('(man lacks --encoding arg on OSX, BSD, and Slackware #18660)')
- return
manual = stem.manual.Manual.from_man(EXAMPLE_MAN_PATH)
@@ -175,7 +163,6 @@ class TestManual(unittest.TestCase):
if not stem.manual.HAS_ENCODING_ARG:
self.skipTest('(man lacks --encoding arg on OSX and BSD and Slackware, #18660)')
- return
manual = stem.manual.Manual.from_man(UNKNOWN_OPTIONS_MAN_PATH)
@@ -203,7 +190,6 @@ class TestManual(unittest.TestCase):
if not stem.manual.HAS_ENCODING_ARG:
self.skipTest('(man lacks --encoding arg on OSX, BSD and Slackware, #18660)')
- return
manual = stem.manual.Manual.from_man(EXAMPLE_MAN_PATH)
@@ -220,7 +206,6 @@ class TestManual(unittest.TestCase):
if not stem.manual.HAS_ENCODING_ARG:
self.skipTest('(man lacks --encoding arg on OSX, BSD, and Slackware #18660)')
- return
manual = stem.manual.Manual.from_man(EXAMPLE_MAN_PATH)
@@ -249,40 +234,36 @@ class TestManual(unittest.TestCase):
exc_msg = 'We require a2x from asciidoc to provide a man page'
self.assertRaisesWith(IOError, exc_msg, stem.manual.download_man_page, '/tmp/no_such_file')
- @patch('tempfile.mkdtemp', Mock(return_value = '/no/such/path'))
- @patch('shutil.rmtree', Mock())
+ @patch('tempfile.TemporaryDirectory', Mock(return_value = TEMP_DIR_MOCK))
@patch('stem.manual.open', Mock(side_effect = IOError('unable to write to file')), create = True)
@patch('stem.util.system.is_available', Mock(return_value = True))
def test_download_man_page_when_unable_to_write(self):
exc_msg = "Unable to download tor's manual from https://gitweb.torproject.org/tor.git/plain/doc/tor.1.txt to /no/such/path/tor.1.txt: unable to write to file"
self.assertRaisesWith(IOError, exc_msg, stem.manual.download_man_page, '/tmp/no_such_file')
- @patch('tempfile.mkdtemp', Mock(return_value = '/no/such/path'))
- @patch('shutil.rmtree', Mock())
+ @patch('tempfile.TemporaryDirectory', Mock(return_value = TEMP_DIR_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.request.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')
- @patch('tempfile.mkdtemp', Mock(return_value = '/no/such/path'))
- @patch('shutil.rmtree', Mock())
+ @patch('tempfile.TemporaryDirectory', Mock(return_value = TEMP_DIR_MOCK))
@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')
- @patch('tempfile.mkdtemp', Mock(return_value = '/no/such/path'))
- @patch('shutil.rmtree', Mock())
+ @patch('tempfile.TemporaryDirectory', Mock(return_value = TEMP_DIR_MOCK))
@patch('stem.manual.open', create = True)
@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(),
@@ -315,4 +296,4 @@ class TestManual(unittest.TestCase):
self.assertEqual({}, manual.commandline_options)
self.assertEqual({}, manual.signals)
self.assertEqual({}, manual.files)
- self.assertEqual(OrderedDict(), manual.config_options)
+ self.assertEqual(collections.OrderedDict(), manual.config_options)
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index 82506e6d..27862054 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -10,16 +10,12 @@ import stem.response
import stem.response.events
import stem.util.log
+from unittest.mock import Mock
+
from stem import * # enums and exceptions
from stem.response import ControlMessage
from stem.descriptor.router_status_entry import RouterStatusEntryV3
-try:
- # added in python 3.3
- from unittest.mock import Mock
-except ImportError:
- from mock import Mock
-
# ADDRMAP event
ADDRMAP = '650 ADDRMAP www.atagar.com 75.119.206.243 "2012-11-19 00:50:13" \
diff --git a/test/unit/response/protocolinfo.py b/test/unit/response/protocolinfo.py
index ab6dd0eb..dd8d2160 100644
--- a/test/unit/response/protocolinfo.py
+++ b/test/unit/response/protocolinfo.py
@@ -11,15 +11,11 @@ import stem.util.proc
import stem.util.system
import stem.version
+from unittest.mock import Mock, patch
+
from stem.response import ControlMessage
from stem.response.protocolinfo import AuthMethod
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
-
NO_AUTH = """250-PROTOCOLINFO 1
250-AUTH METHODS=NULL
250-VERSION Tor="0.2.1.30"
diff --git a/test/unit/tutorial.py b/test/unit/tutorial.py
index e866f3ca..91a70a32 100644
--- a/test/unit/tutorial.py
+++ b/test/unit/tutorial.py
@@ -7,6 +7,8 @@ import unittest
import stem.descriptor.remote
+from unittest.mock import Mock, patch
+
from stem.control import Controller
from stem.descriptor.router_status_entry import RouterStatusEntryV2, RouterStatusEntryV3
from stem.descriptor.networkstatus import NetworkStatusDocumentV3
@@ -14,17 +16,6 @@ from stem.descriptor.server_descriptor import RelayDescriptor
from stem.exit_policy import ExitPolicy
from test.unit import exec_documentation_example
-try:
- from StringIO import StringIO
-except ImportError:
- from io import StringIO
-
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
-
OVER_THE_RIVER_OUTPUT = """\
* Connecting to tor
@@ -47,7 +38,7 @@ class TestTutorial(unittest.TestCase):
stem.descriptor.remote.SINGLETON_DOWNLOADER = None
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
@patch('stem.control.Controller.from_port', spec = Controller)
def test_the_little_relay_that_could(self, from_port_mock, stdout_mock):
controller = from_port_mock().__enter__()
@@ -59,7 +50,7 @@ class TestTutorial(unittest.TestCase):
exec_documentation_example('hello_world.py')
self.assertEqual('My Tor relay has read 33406 bytes and written 29649.\n', stdout_mock.getvalue())
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
@patch('shutil.rmtree')
@patch('stem.control.Controller.from_port', spec = Controller)
def test_over_the_river(self, from_port_mock, rmtree_mock, stdout_mock):
@@ -125,7 +116,7 @@ class TestTutorial(unittest.TestCase):
self.assertEqual(OVER_THE_RIVER_OUTPUT, stdout_mock.getvalue())
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
@patch('stem.descriptor.remote.DescriptorDownloader')
def test_mirror_mirror_on_the_wall_1(self, downloader_mock, stdout_mock):
downloader_mock().get_consensus.return_value = [RouterStatusEntryV2.create({
@@ -135,7 +126,7 @@ class TestTutorial(unittest.TestCase):
exec_documentation_example('current_descriptors.py')
self.assertEqual('found relay caerSidi (A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB)\n', stdout_mock.getvalue())
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
@patch('stem.control.Controller.from_port', spec = Controller)
def test_mirror_mirror_on_the_wall_2(self, from_port_mock, stdout_mock):
controller = from_port_mock().__enter__()
@@ -146,7 +137,7 @@ class TestTutorial(unittest.TestCase):
exec_documentation_example('descriptor_from_tor_control_socket.py')
self.assertEqual('found relay caerSidi (A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB)\n', stdout_mock.getvalue())
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
@patch('%s.open' % __name__, create = True)
def test_mirror_mirror_on_the_wall_3(self, open_mock, stdout_mock):
def tutorial_example():
@@ -164,7 +155,7 @@ class TestTutorial(unittest.TestCase):
tutorial_example()
self.assertEqual('found relay caerSidi (A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB)\n', stdout_mock.getvalue())
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
@patch('stem.descriptor.collector.get_server_descriptors')
def test_mirror_mirror_on_the_wall_4(self, get_desc_mock, stdout_mock):
get_desc_mock.return_value = iter([RelayDescriptor.create({
@@ -175,7 +166,7 @@ class TestTutorial(unittest.TestCase):
exec_documentation_example('collector_reading.py')
self.assertEqual('1 relays published an exiting policy today...\n\n caerSidi (2C3C46625698B6D67DF32BC1918AD3EE1F9906B1)\n', stdout_mock.getvalue())
- @patch('sys.stdout', new_callable = StringIO)
+ @patch('sys.stdout', new_callable = io.StringIO)
@patch('stem.descriptor.remote.DescriptorDownloader')
@patch('stem.prereq.is_crypto_available', Mock(return_value = False))
def test_mirror_mirror_on_the_wall_5(self, downloader_mock, stdout_mock):
diff --git a/test/unit/tutorial_examples.py b/test/unit/tutorial_examples.py
index c4b26ad0..d96ff71b 100644
--- a/test/unit/tutorial_examples.py
+++ b/test/unit/tutorial_examples.py
@@ -2,19 +2,17 @@
Tests for the examples given in stem's tutorial.
"""
+import io
import itertools
import os
import unittest
-try:
- from StringIO import StringIO
-except ImportError:
- from io import StringIO
-
import stem.response
import stem.descriptor.remote
import stem.prereq
+from unittest.mock import Mock, patch
+
from stem.control import Controller
from stem.descriptor.networkstatus import NetworkStatusDocumentV3
from stem.descriptor.router_status_entry import RouterStatusEntryV3
@@ -24,12 +22,6 @@ from stem.response import ControlMessage
from test.unit import exec_documentation_example
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
-
OPEN_FUNCTION = open # make a reference so mocking open() won't mess with us
CIRC_CONTENT = '650 CIRC %d %s \
@@ -129,13 +121,7 @@ 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('sys.stdout', new_callable = io.StringIO)
@patch('stem.control.Controller.from_port', spec = Controller)
def test_list_circuits(self, from_port_mock, stdout_mock):
path_1 = ('B1FA7D51B8B6F0CB585D944F450E7C06EDE7E44C', 'ByTORAndTheSnowDog')
@@ -164,9 +150,9 @@ 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('sys.stdout', new_callable = io.StringIO)
@patch('stem.control.Controller.from_port', spec = Controller)
def test_exit_used(self, from_port_mock, stdout_mock):
def tutorial_example(mock_event):
@@ -215,9 +201,9 @@ 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('sys.stdout', new_callable = io.StringIO)
@patch('stem.descriptor.remote.DescriptorDownloader')
def test_outdated_relays(self, downloader_mock, stdout_mock):
downloader_mock().get_server_descriptors.return_value = [
@@ -229,19 +215,12 @@ 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('sys.stdout', new_callable = io.StringIO)
@patch('stem.descriptor.remote.Query')
@patch('stem.directory.Authority.from_cache')
def test_compare_flags(self, authorities_mock, query_mock, stdout_mock):
- if stem.prereq._is_python_26():
- # example imports OrderedDict from collections which doesn't work under
- # python 2.6
-
- self.skipTest("(example doesn't support python 2.6)")
- return
-
authorities_mock().items.return_value = [('moria1', DIRECTORY_AUTHORITIES['moria1']), ('maatuska', DIRECTORY_AUTHORITIES['maatuska'])]
fingerprint = [
@@ -277,9 +256,9 @@ 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('sys.stdout', new_callable = io.StringIO)
@patch('stem.directory.Authority.from_cache')
@patch('stem.descriptor.remote.DescriptorDownloader.query')
def test_votes_by_bandwidth_authorities(self, query_mock, authorities_mock, stdout_mock):
@@ -310,9 +289,9 @@ 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('sys.stdout', new_callable = io.StringIO)
@patch('stem.descriptor.parse_file')
@patch('stem.descriptor.remote.Query')
def test_persisting_a_consensus(self, query_mock, parse_file_mock, stdout_mock):
diff --git a/test/unit/util/connection.py b/test/unit/util/connection.py
index 8721ff6d..d848c40b 100644
--- a/test/unit/util/connection.py
+++ b/test/unit/util/connection.py
@@ -5,25 +5,15 @@ Unit tests for the stem.util.connection functions.
import io
import platform
import unittest
+import urllib.request
import stem
import stem.util.connection
-from stem.util.connection import Resolver, Connection
-
-try:
- # account for urllib's change between python 2.x and 3.x
- import urllib.request as urllib
-except ImportError:
- import urllib2 as urllib
+from unittest.mock import Mock, patch
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
+from stem.util.connection import Resolver, Connection
-URL_OPEN = 'urllib.request.urlopen' if stem.prereq.is_python_3() else 'urllib2.urlopen'
URL = 'https://example.unit.test.url'
NETSTAT_OUTPUT = """\
@@ -177,16 +167,16 @@ _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')
+ urlopen_mock.side_effect = urllib.request.URLError('boom')
try:
stem.util.connection.download(URL)
@@ -195,12 +185,12 @@ class TestConnection(unittest.TestCase):
self.assertEqual('Failed to download from https://example.unit.test.url (URLError): boom', str(exc))
self.assertEqual(URL, exc.url)
self.assertEqual('boom', exc.error.reason)
- self.assertEqual(urllib.URLError, type(exc.error))
- self.assertTrue('return urllib.urlopen(url, timeout = timeout).read()' in exc.stacktrace_str)
+ self.assertEqual(urllib.request.URLError, type(exc.error))
+ self.assertTrue('return urllib.request.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')
+ urlopen_mock.side_effect = urllib.request.URLError('boom')
self.assertRaisesRegexp(IOError, 'boom', stem.util.connection.download, URL)
self.assertEqual(1, urlopen_mock.call_count)
diff --git a/test/unit/util/proc.py b/test/unit/util/proc.py
index 6ee29136..2316f669 100644
--- a/test/unit/util/proc.py
+++ b/test/unit/util/proc.py
@@ -7,14 +7,11 @@ import unittest
import test
+from unittest.mock import Mock, patch
+
from stem.util import proc
from stem.util.connection import Connection
-try:
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
-
TITLE_LINE = b'sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout'
TCP6_CONTENT = b"""\
diff --git a/test/unit/util/system.py b/test/unit/util/system.py
index b4fb81ea..32be337c 100644
--- a/test/unit/util/system.py
+++ b/test/unit/util/system.py
@@ -14,13 +14,9 @@ import unittest
import stem.prereq
-from stem.util import system
+from unittest.mock import Mock, patch
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
+from stem.util import system
# Base responses for the pid_by_name tests. The 'success' and
# 'multiple_results' entries are filled in by tests.
diff --git a/test/unit/version.py b/test/unit/version.py
index 3c21855c..abdb65c0 100644
--- a/test/unit/version.py
+++ b/test/unit/version.py
@@ -7,13 +7,9 @@ import unittest
import stem.util.system
import stem.version
-from stem.version import Version
+from unittest.mock import Mock, patch
-try:
- # added in python 3.3
- from unittest.mock import Mock, patch
-except ImportError:
- from mock import Mock, patch
+from stem.version import Version
VERSION_CMD_OUTPUT = """Mar 22 23:09:37.088 [notice] Tor v0.2.2.35 \
(git-73ff13ab3cc9570d). This is experimental software. Do not rely on it for \