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