[tor-commits] [stem/master] Replace all IOErrors with OSErrors

atagar at torproject.org atagar at torproject.org
Tue Sep 1 21:09:03 UTC 2020


commit 4164c7a6203fae6671075dfba69461340dd05bc5
Author: Damian Johnson <atagar at torproject.org>
Date:   Tue Sep 1 13:57:23 2020 -0700

    Replace all IOErrors with OSErrors
    
    PEP 3151 deprecated IOError...
    
      https://www.python.org/dev/peps/pep-3151/#confusing-set-of-os-related-exceptions
    
    Python 3.3 turned IOError into an OSError alias, so this commit shouldn't
    impact our users...
    
      >>> raise OSError('boom')
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      OSError: boom
    
      >>> raise IOError('boom')
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      OSError: boom
---
 cache_fallback_directories.py                 |  2 +-
 cache_manual.py                               |  4 +-
 docs/_static/example/check_digests.py         |  2 +-
 docs/_static/example/manual_config_options.py |  2 +-
 docs/_static/example/reading_twitter.py       |  4 +-
 docs/change_log.rst                           |  1 +
 stem/__init__.py                              |  2 +-
 stem/connection.py                            |  2 +-
 stem/descriptor/__init__.py                   | 10 ++---
 stem/descriptor/bandwidth_file.py             |  2 +-
 stem/descriptor/collector.py                  |  8 ++--
 stem/descriptor/extrainfo_descriptor.py       |  2 +-
 stem/descriptor/hidden_service.py             |  2 +-
 stem/descriptor/microdescriptor.py            |  2 +-
 stem/descriptor/networkstatus.py              |  6 +--
 stem/descriptor/router_status_entry.py        |  2 +-
 stem/descriptor/server_descriptor.py          |  2 +-
 stem/descriptor/tordnsel.py                   |  2 +-
 stem/directory.py                             | 18 ++++----
 stem/interpreter/__init__.py                  |  2 +-
 stem/manual.py                                | 26 +++++------
 stem/util/conf.py                             |  8 ++--
 stem/util/connection.py                       | 12 +++---
 stem/util/proc.py                             | 62 +++++++++++++--------------
 stem/util/system.py                           | 16 +++----
 stem/version.py                               | 12 +++---
 test/integ/util/connection.py                 |  2 +-
 test/integ/version.py                         |  4 +-
 test/unit/descriptor/collector.py             |  8 ++--
 test/unit/directory/fallback.py               |  6 +--
 test/unit/manual.py                           | 12 +++---
 test/unit/util/connection.py                  | 48 ++++++++++-----------
 test/unit/util/proc.py                        |  4 +-
 test/unit/util/system.py                      |  4 +-
 test/unit/version.py                          |  4 +-
 35 files changed, 153 insertions(+), 152 deletions(-)

diff --git a/cache_fallback_directories.py b/cache_fallback_directories.py
index 7f712683..8fe425d1 100755
--- a/cache_fallback_directories.py
+++ b/cache_fallback_directories.py
@@ -26,7 +26,7 @@ if __name__ == '__main__':
 
   try:
     stem_commit = stem.util.system.call('git rev-parse HEAD')[0]
-  except IOError as exc:
+  except OSError as exc:
     print("Unable to determine stem's current commit: %s" % exc)
     sys.exit(1)
 
diff --git a/cache_manual.py b/cache_manual.py
index 803197f1..4ddb843f 100755
--- a/cache_manual.py
+++ b/cache_manual.py
@@ -26,7 +26,7 @@ if __name__ == '__main__':
 
   try:
     stem_commit = stem.util.system.call('git rev-parse HEAD')[0]
-  except IOError as exc:
+  except OSError as exc:
     print("Unable to determine stem's current commit: %s" % exc)
     sys.exit(1)
 
@@ -39,7 +39,7 @@ if __name__ == '__main__':
     db_schema = cached_manual.schema
   except stem.manual.SchemaMismatch as exc:
     cached_manual, db_schema = None, exc.database_schema
-  except IOError:
+  except OSError:
     cached_manual, db_schema = None, None  # local copy has been deleted
 
   if db_schema != stem.manual.SCHEMA_VERSION:
diff --git a/docs/_static/example/check_digests.py b/docs/_static/example/check_digests.py
index 2be3c368..69f509cf 100644
--- a/docs/_static/example/check_digests.py
+++ b/docs/_static/example/check_digests.py
@@ -19,7 +19,7 @@ def download_descriptors(fingerprint):
   router_status_entries = filter(lambda desc: desc.fingerprint == fingerprint, conensus_query.run())
 
   if len(router_status_entries) != 1:
-    raise IOError("Unable to find relay '%s' in the consensus" % fingerprint)
+    raise OSError("Unable to find relay '%s' in the consensus" % fingerprint)
 
   return (
     router_status_entries[0],
diff --git a/docs/_static/example/manual_config_options.py b/docs/_static/example/manual_config_options.py
index 964ff523..4a503579 100644
--- a/docs/_static/example/manual_config_options.py
+++ b/docs/_static/example/manual_config_options.py
@@ -5,7 +5,7 @@ try:
   print("Downloading tor's manual information, please wait...")
   manual = Manual.from_remote()
   print("  done\n")
-except IOError as exc:
+except OSError as exc:
   print("  unsuccessful (%s), using information provided with stem\n" % exc)
   manual = Manual.from_cache()  # fall back to our bundled manual information
 
diff --git a/docs/_static/example/reading_twitter.py b/docs/_static/example/reading_twitter.py
index 7f9094b3..5709e1f4 100644
--- a/docs/_static/example/reading_twitter.py
+++ b/docs/_static/example/reading_twitter.py
@@ -65,7 +65,7 @@ def poll_twitter_feed(user_id, tweet_count):
   try:
     api_response = urllib2.urlopen(api_request).read()
   except:
-    raise IOError("Unable to reach %s" % TWITTER_API_URL)
+    raise OSError("Unable to reach %s" % TWITTER_API_URL)
 
   return json.loads(api_response)
 
@@ -81,7 +81,7 @@ try:
     print("%i. %s" % (index + 1, tweet["created_at"]))
     print(tweet["text"])
     print("")
-except IOError as exc:
+except OSError as exc:
   print(exc)
 finally:
   tor_process.kill()  # stops tor
diff --git a/docs/change_log.rst b/docs/change_log.rst
index f5a96d7b..bbd13ef5 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -54,6 +54,7 @@ The following are only available within Stem's `git repository
   * Migrated to `asyncio <https://docs.python.org/3/library/asyncio.html>`_. Stem can now be used by `both synchronous and asynchronous applications <https://blog.atagar.com/july2020/>`_.
   * Installation has migrated from distutils to setuptools.
   * Added the 'reset_timeouts' argument to :func:`~stem.control.Controller.drop_guards` (:ticket:`73`)
+  * Replace all IOErrors with OSErrors. Python 3.3 changed IOError into an `OSError alias <https://docs.python.org/3/library/exceptions.html#OSError>`_ to `deprecate it <https://www.python.org/dev/peps/pep-3151/#confusing-set-of-os-related-exceptions>`_.
 
  * **Controller**
 
diff --git a/stem/__init__.py b/stem/__init__.py
index 228ec7be..e8782115 100644
--- a/stem/__init__.py
+++ b/stem/__init__.py
@@ -731,7 +731,7 @@ class SocketClosed(SocketError):
   'Control socket was closed before completing the message.'
 
 
-class DownloadFailed(IOError):
+class DownloadFailed(OSError):
   """
   Inability to download a resource. Python's urllib module raises
   a wide variety of undocumented exceptions (urllib.request.URLError,
diff --git a/stem/connection.py b/stem/connection.py
index 0dbbcfbf..f5f92464 100644
--- a/stem/connection.py
+++ b/stem/connection.py
@@ -1157,7 +1157,7 @@ def _read_cookie(cookie_path: str, is_safecookie: bool) -> bytes:
   try:
     with open(cookie_path, 'rb', 0) as f:
       return f.read()
-  except IOError as exc:
+  except OSError as exc:
     exc_msg = "Authentication failed: unable to read '%s' (%s)" % (cookie_path, exc)
     raise UnreadableCookieFile(exc_msg, cookie_path, is_safecookie)
 
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index df947cf7..58a88d55 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -238,7 +238,7 @@ class _Compression(object):
     :raises:
       If unable to decompress this provide...
 
-      * **IOError** if content isn't compressed with this
+      * **OSError** if content isn't compressed with this
       * **ImportError** if this method if decompression is unavalable
     """
 
@@ -253,7 +253,7 @@ class _Compression(object):
     try:
       return self._decompression_func(self._module, content)
     except Exception as exc:
-      raise IOError('Failed to decompress as %s: %s' % (self, exc))
+      raise OSError('Failed to decompress as %s: %s' % (self, exc))
 
   def __str__(self) -> str:
     return self._name
@@ -370,7 +370,7 @@ def parse_file(descriptor_file: Union[str, BinaryIO, tarfile.TarFile, IO[bytes]]
   :raises:
     * **ValueError** if the contents is malformed and validate is True
     * **TypeError** if we can't match the contents of the file to a descriptor type
-    * **IOError** if unable to read from the descriptor_file
+    * **OSError** if unable to read from the descriptor_file
   """
 
   # Delegate to a helper if this is a path or tarfile.
@@ -392,7 +392,7 @@ def parse_file(descriptor_file: Union[str, BinaryIO, tarfile.TarFile, IO[bytes]]
     return
 
   if not descriptor_file.seekable():  # type: ignore
-    raise IOError(UNSEEKABLE_MSG)
+    raise OSError(UNSEEKABLE_MSG)
 
   # The tor descriptor specifications do not provide a reliable method for
   # identifying a descriptor file's type and version so we need to guess
@@ -860,7 +860,7 @@ class Descriptor(object):
     :raises:
       * **ValueError** if the contents is malformed and validate is True
       * **TypeError** if we can't match the contents of the file to a descriptor type
-      * **IOError** if unable to read from the descriptor_file
+      * **OSError** if unable to read from the descriptor_file
     """
 
     if 'descriptor_type' not in kwargs and cls.TYPE_ANNOTATION_NAME is not None:
diff --git a/stem/descriptor/bandwidth_file.py b/stem/descriptor/bandwidth_file.py
index 2b1ed5d3..f449665d 100644
--- a/stem/descriptor/bandwidth_file.py
+++ b/stem/descriptor/bandwidth_file.py
@@ -175,7 +175,7 @@ def _parse_file(descriptor_file: BinaryIO, validate: bool = False, **kwargs: Any
 
   :raises:
     * **ValueError** if the contents is malformed and validate is **True**
-    * **IOError** if the file can't be read
+    * **OSError** if the file can't be read
   """
 
   if kwargs:
diff --git a/stem/descriptor/collector.py b/stem/descriptor/collector.py
index 8c05a832..892ccc30 100644
--- a/stem/descriptor/collector.py
+++ b/stem/descriptor/collector.py
@@ -307,7 +307,7 @@ class File(object):
 
     :raises:
       * :class:`~stem.DownloadFailed` if the download fails
-      * **IOError** if a mismatching file exists and **overwrite** is **False**
+      * **OSError** if a mismatching file exists and **overwrite** is **False**
     """
 
     filename = self.path.split('/')[-1]
@@ -332,7 +332,7 @@ class File(object):
         if expected_hash == actual_hash:
           return path  # nothing to do, we already have the file
         elif not overwrite:
-          raise IOError("%s already exists but mismatches CollecTor's checksum (expected: %s, actual: %s)" % (path, expected_hash, actual_hash))
+          raise OSError("%s already exists but mismatches CollecTor's checksum (expected: %s, actual: %s)" % (path, expected_hash, actual_hash))
 
     response = stem.util.connection.download(COLLECTOR_URL + self.path, timeout, retries)
 
@@ -624,7 +624,7 @@ class CollecTor(object):
       If unable to retrieve the index this provide...
 
         * **ValueError** if json is malformed
-        * **IOError** if unable to decompress
+        * **OSError** if unable to decompress
         * :class:`~stem.DownloadFailed` if the download fails
     """
 
@@ -664,7 +664,7 @@ class CollecTor(object):
       If unable to retrieve the index this provide...
 
         * **ValueError** if json is malformed
-        * **IOError** if unable to decompress
+        * **OSError** if unable to decompress
         * :class:`~stem.DownloadFailed` if the download fails
     """
 
diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py
index 86458cdf..8d9cbf30 100644
--- a/stem/descriptor/extrainfo_descriptor.py
+++ b/stem/descriptor/extrainfo_descriptor.py
@@ -182,7 +182,7 @@ def _parse_file(descriptor_file: BinaryIO, is_bridge = False, validate = False,
 
   :raises:
     * **ValueError** if the contents is malformed and validate is **True**
-    * **IOError** if the file can't be read
+    * **OSError** if the file can't be read
   """
 
   if kwargs:
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index 49737f9d..dc86b382 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -451,7 +451,7 @@ def _parse_file(descriptor_file: BinaryIO, desc_type: Optional[Type['stem.descri
 
   :raises:
     * **ValueError** if the contents is malformed and validate is **True**
-    * **IOError** if the file can't be read
+    * **OSError** if the file can't be read
   """
 
   if desc_type is None:
diff --git a/stem/descriptor/microdescriptor.py b/stem/descriptor/microdescriptor.py
index 0b015ebb..ea48fce8 100644
--- a/stem/descriptor/microdescriptor.py
+++ b/stem/descriptor/microdescriptor.py
@@ -108,7 +108,7 @@ def _parse_file(descriptor_file: BinaryIO, validate: bool = False, **kwargs: Any
 
   :raises:
     * **ValueError** if the contents is malformed and validate is True
-    * **IOError** if the file can't be read
+    * **OSError** if the file can't be read
   """
 
   if kwargs:
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index f216e427..e8cf90eb 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -317,7 +317,7 @@ def _parse_file(document_file: BinaryIO, document_type: Optional[Type] = None, v
   :raises:
     * **ValueError** if the document_version is unrecognized or the contents is
       malformed and validate is **True**
-    * **IOError** if the file can't be read
+    * **OSError** if the file can't be read
   """
 
   # we can't properly default this since NetworkStatusDocumentV3 isn't defined yet
@@ -390,7 +390,7 @@ def _parse_file_key_certs(certificate_file: BinaryIO, validate: bool = False) ->
 
   :raises:
     * **ValueError** if the key certificates are invalid and validate is **True**
-    * **IOError** if the file can't be read
+    * **OSError** if the file can't be read
   """
 
   while True:
@@ -419,7 +419,7 @@ def _parse_file_detached_sigs(detached_signature_file: BinaryIO, validate: bool
 
   :raises:
     * **ValueError** if the detached signatures are invalid and validate is **True**
-    * **IOError** if the file can't be read
+    * **OSError** if the file can't be read
   """
 
   while True:
diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py
index ecfcb7fd..aa94a703 100644
--- a/stem/descriptor/router_status_entry.py
+++ b/stem/descriptor/router_status_entry.py
@@ -72,7 +72,7 @@ def _parse_file(document_file: BinaryIO, validate: bool, entry_class: Type['stem
 
   :raises:
     * **ValueError** if the contents is malformed and validate is **True**
-    * **IOError** if the file can't be read
+    * **OSError** if the file can't be read
   """
 
   if start_position:
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index 6e6a5369..e49688e1 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -159,7 +159,7 @@ def _parse_file(descriptor_file: BinaryIO, is_bridge: bool = False, validate: bo
 
   :raises:
     * **ValueError** if the contents is malformed and validate is True
-    * **IOError** if the file can't be read
+    * **OSError** if the file can't be read
   """
 
   # Handler for relay descriptors
diff --git a/stem/descriptor/tordnsel.py b/stem/descriptor/tordnsel.py
index 6b9d4ceb..32cf1863 100644
--- a/stem/descriptor/tordnsel.py
+++ b/stem/descriptor/tordnsel.py
@@ -35,7 +35,7 @@ def _parse_file(tordnsel_file: BinaryIO, validate: bool = False, **kwargs: Any)
 
   :raises:
     * **ValueError** if the contents is malformed and validate is **True**
-    * **IOError** if the file can't be read
+    * **OSError** if the file can't be read
   """
 
   if kwargs:
diff --git a/stem/directory.py b/stem/directory.py
index 20982ccc..39abfb90 100644
--- a/stem/directory.py
+++ b/stem/directory.py
@@ -192,7 +192,7 @@ class Directory(object):
 
       try:
         authorities = stem.directory.Authority.from_remote()
-      except IOError:
+      except OSError:
         authorities = stem.directory.Authority.from_cache()
 
     .. versionadded:: 1.5.0
@@ -205,7 +205,7 @@ class Directory(object):
     :returns: **dict** of **str** identifiers to their
       :class:`~stem.directory.Directory`
 
-    :raises: **IOError** if unable to retrieve the fallback directories
+    :raises: **OSError** if unable to retrieve the fallback directories
     """
 
     raise NotImplementedError('Unsupported Operation: this should be implemented by the Directory subclass')
@@ -251,7 +251,7 @@ class Authority(Directory):
       lines = str_tools._to_unicode(urllib.request.urlopen(GITWEB_AUTHORITY_URL, timeout = timeout).read()).splitlines()
 
       if not lines:
-        raise IOError('no content')
+        raise OSError('no content')
     except:
       exc, stacktrace = sys.exc_info()[1:3]
       message = "Unable to download tor's directory authorities from %s: %s" % (GITWEB_AUTHORITY_URL, exc)
@@ -280,7 +280,7 @@ class Authority(Directory):
           v3ident = matches.get(AUTHORITY_V3IDENT),  # type: ignore
         )
     except ValueError as exc:
-      raise IOError(str(exc))
+      raise OSError(str(exc))
 
     return results
 
@@ -373,7 +373,7 @@ class Fallback(Directory):
         attr[attr_name] = conf.get(key)
 
         if not attr[attr_name] and attr_name not in ('nickname', 'has_extrainfo', 'orport6_address', 'orport6_port'):
-          raise IOError("'%s' is missing from %s" % (key, FALLBACK_CACHE_PATH))
+          raise OSError("'%s' is missing from %s" % (key, FALLBACK_CACHE_PATH))
 
       if attr['orport6_address'] and attr['orport6_port']:
         orport_v6 = (attr['orport6_address'], int(attr['orport6_port']))
@@ -399,7 +399,7 @@ class Fallback(Directory):
       lines = str_tools._to_unicode(urllib.request.urlopen(GITWEB_FALLBACK_URL, timeout = timeout).read()).splitlines()
 
       if not lines:
-        raise IOError('no content')
+        raise OSError('no content')
     except:
       exc, stacktrace = sys.exc_info()[1:3]
       message = "Unable to download tor's fallback directories from %s: %s" % (GITWEB_FALLBACK_URL, exc)
@@ -408,7 +408,7 @@ class Fallback(Directory):
     # header metadata
 
     if lines[0] != '/* type=fallback */':
-      raise IOError('%s does not have a type field indicating it is fallback directory metadata' % GITWEB_FALLBACK_URL)
+      raise OSError('%s does not have a type field indicating it is fallback directory metadata' % GITWEB_FALLBACK_URL)
 
     header = {}
 
@@ -418,7 +418,7 @@ class Fallback(Directory):
       if mapping:
         header[mapping.group(1)] = mapping.group(2)
       else:
-        raise IOError('Malformed fallback directory header line: %s' % line)
+        raise OSError('Malformed fallback directory header line: %s' % line)
 
     Fallback._pop_section(lines)  # skip human readable comments
 
@@ -446,7 +446,7 @@ class Fallback(Directory):
           header = header,
         )
     except ValueError as exc:
-      raise IOError(str(exc))
+      raise OSError(str(exc))
 
     return results
 
diff --git a/stem/interpreter/__init__.py b/stem/interpreter/__init__.py
index 1445d9bb..53589bf8 100644
--- a/stem/interpreter/__init__.py
+++ b/stem/interpreter/__init__.py
@@ -137,7 +137,7 @@ def main() -> None:
       try:
         for line in open(args.run_path).readlines():
           interpreter.run_command(line.strip(), print_response = True)
-      except IOError as exc:
+      except OSError as exc:
         print(format(msg('msg.unable_to_read_file', path = args.run_path, error = exc), *ERROR_OUTPUT))
         sys.exit(1)
     else:
diff --git a/stem/manual.py b/stem/manual.py
index 26249abc..dbde9827 100644
--- a/stem/manual.py
+++ b/stem/manual.py
@@ -100,7 +100,7 @@ CATEGORY_SECTIONS = collections.OrderedDict((
 ))
 
 
-class SchemaMismatch(IOError):
+class SchemaMismatch(OSError):
   """
   Database schema doesn't match what Stem supports.
 
@@ -281,13 +281,13 @@ def download_man_page(path: Optional[str] = None, file_handle: Optional[BinaryIO
   :param url: url to download tor's asciidoc manual from
   :param timeout: seconds to wait before timing out the request
 
-  :raises: **IOError** if unable to retrieve the manual
+  :raises: **OSError** if unable to retrieve the manual
   """
 
   if not path and not file_handle:
     raise ValueError("Either the path or file_handle we're saving to must be provided")
   elif not stem.util.system.is_available('a2x'):
-    raise IOError('We require a2x from asciidoc to provide a man page')
+    raise OSError('We require a2x from asciidoc to provide a man page')
 
   with tempfile.TemporaryDirectory() as dirpath:
     asciidoc_path = os.path.join(dirpath, 'tor.1.txt')
@@ -308,7 +308,7 @@ def download_man_page(path: Optional[str] = None, file_handle: Optional[BinaryIO
       if not os.path.exists(manual_path):
         raise OSError('no man page was generated')
     except stem.util.system.CallError as exc:
-      raise IOError("Unable to run '%s': %s" % (exc.command, stem.util.str_tools._to_unicode(exc.stderr)))
+      raise OSError("Unable to run '%s': %s" % (exc.command, stem.util.str_tools._to_unicode(exc.stderr)))
 
     if path:
       try:
@@ -319,7 +319,7 @@ def download_man_page(path: Optional[str] = None, file_handle: Optional[BinaryIO
 
         shutil.copyfile(manual_path, path)
       except OSError as exc:
-        raise IOError(exc)
+        raise OSError(exc)
 
     if file_handle:
       with open(manual_path, 'rb') as manual_file:
@@ -385,7 +385,7 @@ class Manual(object):
     :raises:
       * **ImportError** if cache is sqlite and the sqlite3 module is
         unavailable
-      * **IOError** if a **path** was provided and we were unable to read
+      * **OSError** if a **path** was provided and we were unable to read
         it or the schema is out of date
     """
 
@@ -398,7 +398,7 @@ class Manual(object):
       path = CACHE_PATH
 
     if not os.path.exists(path):
-      raise IOError("%s doesn't exist" % path)
+      raise OSError("%s doesn't exist" % path)
 
     with sqlite3.connect(path) as conn:
       try:
@@ -409,7 +409,7 @@ class Manual(object):
 
         name, synopsis, description, man_commit, stem_commit = conn.execute('SELECT name, synopsis, description, man_commit, stem_commit FROM metadata').fetchone()
       except sqlite3.OperationalError as exc:
-        raise IOError('Failed to read database metadata from %s: %s' % (path, exc))
+        raise OSError('Failed to read database metadata from %s: %s' % (path, exc))
 
       commandline = dict(conn.execute('SELECT name, description FROM commandline').fetchall())
       signals = dict(conn.execute('SELECT name, description FROM signals').fetchall())
@@ -442,7 +442,7 @@ class Manual(object):
 
     :returns: :class:`~stem.manual.Manual` for the system's man page
 
-    :raises: **IOError** if unable to retrieve the manual
+    :raises: **OSError** if unable to retrieve the manual
     """
 
     man_cmd = 'man %s -P cat %s' % ('--encoding=ascii' if HAS_ENCODING_ARG else '', man_path)
@@ -450,7 +450,7 @@ class Manual(object):
     try:
       man_output = stem.util.system.call(man_cmd, env = {'MANWIDTH': '10000000'})
     except OSError as exc:
-      raise IOError("Unable to run '%s': %s" % (man_cmd, exc))
+      raise OSError("Unable to run '%s': %s" % (man_cmd, exc))
 
     categories = _get_categories(man_output)
     config_options = collections.OrderedDict()  # type: collections.OrderedDict[str, stem.manual.ConfigOption]
@@ -484,7 +484,7 @@ class Manual(object):
 
       try:
         manual = stem.manual.from_remote()
-      except IOError:
+      except OSError:
         manual = stem.manual.from_cache()
 
     In addition to our GitWeb dependency this requires 'a2x' which is part of
@@ -499,7 +499,7 @@ class Manual(object):
 
     :returns: latest :class:`~stem.manual.Manual` available for tor
 
-    :raises: **IOError** if unable to retrieve the manual
+    :raises: **OSError** if unable to retrieve the manual
     """
 
     with tempfile.NamedTemporaryFile() as tmp:
@@ -519,7 +519,7 @@ class Manual(object):
     :raises:
       * **ImportError** if saving as sqlite and the sqlite3 module is
         unavailable
-      * **IOError** if unsuccessful
+      * **OSError** if unsuccessful
     """
 
     try:
diff --git a/stem/util/conf.py b/stem/util/conf.py
index 1ac0d107..4760d7b4 100644
--- a/stem/util/conf.py
+++ b/stem/util/conf.py
@@ -267,7 +267,7 @@ def uses_settings(handle: str, path: str, lazy_load: bool = True) -> Callable:
   :returns: **function** that can be used as a decorator to provide the
     configuration
 
-  :raises: **IOError** if we fail to read the configuration file, if
+  :raises: **OSError** if we fail to read the configuration file, if
     **lazy_load** is true then this arises when we use the decorator
   """
 
@@ -416,7 +416,7 @@ class Config(object):
 
     try:
       user_config.load("/home/atagar/myConfig")
-    except IOError as exc:
+    except OSError as exc:
       print "Unable to load the user's config: %s" % exc
 
     # This replace the contents of ssh_config with the values from the user's
@@ -485,7 +485,7 @@ class Config(object):
       otherwise
 
     :raises:
-      * **IOError** if we fail to read the file (it doesn't exist, insufficient
+      * **OSError** if we fail to read the file (it doesn't exist, insufficient
         permissions, etc)
       * **ValueError** if no path was provided and we've never been provided one
     """
@@ -547,7 +547,7 @@ class Config(object):
     :param path: location to be saved to
 
     :raises:
-      * **IOError** if we fail to save the file (insufficient permissions, etc)
+      * **OSError** if we fail to save the file (insufficient permissions, etc)
       * **ValueError** if no path was provided and we've never been provided one
     """
 
diff --git a/stem/util/connection.py b/stem/util/connection.py
index f8a21f50..a83922f7 100644
--- a/stem/util/connection.py
+++ b/stem/util/connection.py
@@ -224,7 +224,7 @@ def get_connections(resolver: Optional['stem.util.connection.Resolver'] = None,
   :raises:
     * **ValueError** if neither a process_pid nor process_name is provided
 
-    * **IOError** if no connections are available or resolution fails
+    * **OSError** if no connections are available or resolution fails
       (generally they're indistinguishable). The common causes are the
       command being unavailable or permissions.
   """
@@ -235,7 +235,7 @@ def get_connections(resolver: Optional['stem.util.connection.Resolver'] = None,
     if available_resolvers:
       resolver = available_resolvers[0]
     else:
-      raise IOError('Unable to determine a connection resolver')
+      raise OSError('Unable to determine a connection resolver')
 
   if not process_pid and not process_name:
     raise ValueError('You must provide a pid or process name to provide connections for')
@@ -258,12 +258,12 @@ def get_connections(resolver: Optional['stem.util.connection.Resolver'] = None,
 
     if len(all_pids) == 0:
       if resolver in (Resolver.NETSTAT_WINDOWS, Resolver.PROC, Resolver.BSD_PROCSTAT):
-        raise IOError("Unable to determine the pid of '%s'. %s requires the pid to provide the connections." % (process_name, resolver))
+        raise OSError("Unable to determine the pid of '%s'. %s requires the pid to provide the connections." % (process_name, resolver))
     elif len(all_pids) == 1:
       process_pid = all_pids[0]
     else:
       if resolver in (Resolver.NETSTAT_WINDOWS, Resolver.PROC, Resolver.BSD_PROCSTAT):
-        raise IOError("There's multiple processes named '%s'. %s requires a single pid to provide the connections." % (process_name, resolver))
+        raise OSError("There's multiple processes named '%s'. %s requires a single pid to provide the connections." % (process_name, resolver))
 
   if resolver == Resolver.PROC:
     return stem.util.proc.connections(pid = process_pid)
@@ -273,7 +273,7 @@ def get_connections(resolver: Optional['stem.util.connection.Resolver'] = None,
   try:
     results = stem.util.system.call(resolver_command)
   except OSError as exc:
-    raise IOError("Unable to query '%s': %s" % (resolver_command, exc))
+    raise OSError("Unable to query '%s': %s" % (resolver_command, exc))
 
   resolver_regex_str = RESOLVER_FILTER[resolver].format(
     protocol = '(?P<protocol>\\S+)',
@@ -330,7 +330,7 @@ def get_connections(resolver: Optional['stem.util.connection.Resolver'] = None,
   _log('%i connections found' % len(connections))
 
   if not connections:
-    raise IOError('No results found using: %s' % resolver_command)
+    raise OSError('No results found using: %s' % resolver_command)
 
   return connections
 
diff --git a/stem/util/proc.py b/stem/util/proc.py
index 802937b5..106c666b 100644
--- a/stem/util/proc.py
+++ b/stem/util/proc.py
@@ -108,7 +108,7 @@ def system_start_time() -> float:
 
   :returns: **float** for the unix time of when the system started
 
-  :raises: **IOError** if it can't be determined
+  :raises: **OSError** if it can't be determined
   """
 
   start_time, parameter = time.time(), 'system start time'
@@ -119,7 +119,7 @@ def system_start_time() -> float:
     _log_runtime(parameter, '/proc/stat[btime]', start_time)
     return result
   except:
-    exc = IOError('unable to parse the /proc/stat btime entry: %s' % btime_line)
+    exc = OSError('unable to parse the /proc/stat btime entry: %s' % btime_line)
     _log_failure(parameter, exc)
     raise exc
 
@@ -131,7 +131,7 @@ def physical_memory() -> int:
 
   :returns: **int** for the bytes of physical memory this system has
 
-  :raises: **IOError** if it can't be determined
+  :raises: **OSError** if it can't be determined
   """
 
   start_time, parameter = time.time(), 'system physical memory'
@@ -142,7 +142,7 @@ def physical_memory() -> int:
     _log_runtime(parameter, '/proc/meminfo[MemTotal]', start_time)
     return result
   except:
-    exc = IOError('unable to parse the /proc/meminfo MemTotal entry: %s' % mem_total_line)
+    exc = OSError('unable to parse the /proc/meminfo MemTotal entry: %s' % mem_total_line)
     _log_failure(parameter, exc)
     raise exc
 
@@ -155,7 +155,7 @@ def cwd(pid: int) -> str:
 
   :returns: **str** with the path of the working directory for the process
 
-  :raises: **IOError** if it can't be determined
+  :raises: **OSError** if it can't be determined
   """
 
   start_time, parameter = time.time(), 'cwd'
@@ -167,7 +167,7 @@ def cwd(pid: int) -> str:
     try:
       cwd = os.readlink(proc_cwd_link)
     except OSError:
-      exc = IOError('unable to read %s' % proc_cwd_link)
+      exc = OSError('unable to read %s' % proc_cwd_link)
       _log_failure(parameter, exc)
       raise exc
 
@@ -183,7 +183,7 @@ def uid(pid: int) -> int:
 
   :returns: **int** with the user id for the owner of the process
 
-  :raises: **IOError** if it can't be determined
+  :raises: **OSError** if it can't be determined
   """
 
   start_time, parameter = time.time(), 'uid'
@@ -195,7 +195,7 @@ def uid(pid: int) -> int:
     _log_runtime(parameter, '%s[Uid]' % status_path, start_time)
     return result
   except:
-    exc = IOError('unable to parse the %s Uid entry: %s' % (status_path, uid_line))
+    exc = OSError('unable to parse the %s Uid entry: %s' % (status_path, uid_line))
     _log_failure(parameter, exc)
     raise exc
 
@@ -209,7 +209,7 @@ def memory_usage(pid: int) -> Tuple[int, int]:
   :returns: **tuple** of two ints with the memory usage of the process, of the
     form **(resident_size, virtual_size)**
 
-  :raises: **IOError** if it can't be determined
+  :raises: **OSError** if it can't be determined
   """
 
   # checks if this is the kernel process
@@ -228,7 +228,7 @@ def memory_usage(pid: int) -> Tuple[int, int]:
     _log_runtime(parameter, '%s[VmRSS|VmSize]' % status_path, start_time)
     return (residentSize, virtualSize)
   except:
-    exc = IOError('unable to parse the %s VmRSS and VmSize entries: %s' % (status_path, ', '.join(mem_lines)))
+    exc = OSError('unable to parse the %s VmRSS and VmSize entries: %s' % (status_path, ', '.join(mem_lines)))
     _log_failure(parameter, exc)
     raise exc
 
@@ -243,11 +243,11 @@ def stats(pid: int, *stat_types: 'stem.util.proc.Stat') -> Sequence[str]:
 
   :returns: **tuple** with all of the requested statistics as strings
 
-  :raises: **IOError** if it can't be determined
+  :raises: **OSError** if it can't be determined
   """
 
   if CLOCK_TICKS is None:
-    raise IOError('Unable to look up SC_CLK_TCK')
+    raise OSError('Unable to look up SC_CLK_TCK')
 
   start_time, parameter = time.time(), 'process %s' % ', '.join(stat_types)
 
@@ -266,7 +266,7 @@ def stats(pid: int, *stat_types: 'stem.util.proc.Stat') -> Sequence[str]:
     stat_comp += stat_line[cmd_end + 1:].split()
 
   if len(stat_comp) < 44 and _is_float(stat_comp[13], stat_comp[14], stat_comp[21]):
-    exc = IOError('stat file had an unexpected format: %s' % stat_path)
+    exc = OSError('stat file had an unexpected format: %s' % stat_path)
     _log_failure(parameter, exc)
     raise exc
 
@@ -312,21 +312,21 @@ def file_descriptors_used(pid: int) -> int:
 
   :returns: **int** of the number of file descriptors used
 
-  :raises: **IOError** if it can't be determined
+  :raises: **OSError** if it can't be determined
   """
 
   try:
     pid = int(pid)
 
     if pid < 0:
-      raise IOError("Process pids can't be negative: %s" % pid)
+      raise OSError("Process pids can't be negative: %s" % pid)
   except (ValueError, TypeError):
-    raise IOError('Process pid was non-numeric: %s' % pid)
+    raise OSError('Process pid was non-numeric: %s' % pid)
 
   try:
     return len(os.listdir('/proc/%i/fd' % pid))
   except Exception as exc:
-    raise IOError('Unable to check number of file descriptors used: %s' % exc)
+    raise OSError('Unable to check number of file descriptors used: %s' % exc)
 
 
 def connections(pid: Optional[int] = None, user: Optional[str] = None) -> Sequence['stem.util.connection.Connection']:
@@ -340,7 +340,7 @@ def connections(pid: Optional[int] = None, user: Optional[str] = None) -> Sequen
 
   :returns: **list** of :class:`~stem.util.connection.Connection` instances
 
-  :raises: **IOError** if it can't be determined
+  :raises: **OSError** if it can't be determined
   """
 
   start_time, conn = time.time(), []
@@ -352,9 +352,9 @@ def connections(pid: Optional[int] = None, user: Optional[str] = None) -> Sequen
       pid = int(pid)
 
       if pid < 0:
-        raise IOError("Process pids can't be negative: %s" % pid)
+        raise OSError("Process pids can't be negative: %s" % pid)
     except (ValueError, TypeError):
-      raise IOError('Process pid was non-numeric: %s' % pid)
+      raise OSError('Process pid was non-numeric: %s' % pid)
   elif user:
     parameter = 'connections for user %s' % user
   else:
@@ -362,7 +362,7 @@ def connections(pid: Optional[int] = None, user: Optional[str] = None) -> Sequen
 
   try:
     if not IS_PWD_AVAILABLE:
-      raise IOError("This requires python's pwd module, which is unavailable on Windows.")
+      raise OSError("This requires python's pwd module, which is unavailable on Windows.")
 
     inodes = _inodes_for_sockets(pid) if pid else set()
     process_uid = stem.util.str_tools._to_bytes(str(pwd.getpwnam(user).pw_uid)) if user else None
@@ -402,14 +402,14 @@ def connections(pid: Optional[int] = None, user: Optional[str] = None) -> Sequen
               continue  # no port
 
             conn.append(stem.util.connection.Connection(l_addr, l_port, r_addr, r_port, protocol, is_ipv6))
-      except IOError as exc:
-        raise IOError("unable to read '%s': %s" % (proc_file_path, exc))
+      except OSError as exc:
+        raise OSError("unable to read '%s': %s" % (proc_file_path, exc))
       except Exception as exc:
-        raise IOError("unable to parse '%s': %s" % (proc_file_path, exc))
+        raise OSError("unable to parse '%s': %s" % (proc_file_path, exc))
 
     _log_runtime(parameter, '/proc/net/[tcp|udp]', start_time)
     return conn
-  except IOError as exc:
+  except OSError as exc:
     _log_failure(parameter, exc)
     raise
 
@@ -422,7 +422,7 @@ def _inodes_for_sockets(pid: int) -> Set[bytes]:
 
   :returns: **set** with inodes for its sockets
 
-  :raises: **IOError** if it can't be determined
+  :raises: **OSError** if it can't be determined
   """
 
   inodes = set()
@@ -430,7 +430,7 @@ def _inodes_for_sockets(pid: int) -> Set[bytes]:
   try:
     fd_contents = os.listdir('/proc/%s/fd' % pid)
   except OSError as exc:
-    raise IOError('Unable to read our file descriptors: %s' % exc)
+    raise OSError('Unable to read our file descriptors: %s' % exc)
 
   for fd in fd_contents:
     fd_path = '/proc/%s/fd/%s' % (pid, fd)
@@ -447,7 +447,7 @@ def _inodes_for_sockets(pid: int) -> Set[bytes]:
         continue  # descriptors may shift while we're in the middle of iterating over them
 
       # most likely couldn't be read due to permissions
-      raise IOError('unable to determine file descriptor destination (%s): %s' % (exc, fd_path))
+      raise OSError('unable to determine file descriptor destination (%s): %s' % (exc, fd_path))
 
   return inodes
 
@@ -519,7 +519,7 @@ def _get_lines(file_path: str, line_prefixes: Sequence[str], parameter: str) ->
 
   :returns: mapping of prefixes to the matching line
 
-  :raises: **IOError** if unable to read the file or can't find all of the prefixes
+  :raises: **OSError** if unable to read the file or can't find all of the prefixes
   """
 
   try:
@@ -544,10 +544,10 @@ def _get_lines(file_path: str, line_prefixes: Sequence[str], parameter: str) ->
       else:
         msg = '%s did not contain %s entries' % (file_path, ', '.join(remaining_prefixes))
 
-      raise IOError(msg)
+      raise OSError(msg)
     else:
       return results
-  except IOError as exc:
+  except OSError as exc:
     _log_failure(parameter, exc)
     raise
 
diff --git a/stem/util/system.py b/stem/util/system.py
index b0130fb1..5d3e3e2b 100644
--- a/stem/util/system.py
+++ b/stem/util/system.py
@@ -525,7 +525,7 @@ def name_by_pid(pid: int) -> Optional[str]:
   if stem.util.proc.is_available():
     try:
       process_name = stem.util.proc.stats(pid, stem.util.proc.Stat.COMMAND)[0]
-    except IOError:
+    except OSError:
       pass
 
   # attempts to resolve using ps, failing if:
@@ -923,7 +923,7 @@ def cwd(pid: int) -> Optional[str]:
   if stem.util.proc.is_available():
     try:
       return stem.util.proc.cwd(pid)
-    except IOError:
+    except OSError:
       pass
 
   # Fall back to a pwdx query. This isn't available on BSD.
@@ -1024,7 +1024,7 @@ def start_time(pid: str) -> Optional[float]:
   if stem.util.proc.is_available():
     try:
       return float(stem.util.proc.stats(pid, stem.util.proc.Stat.START_TIME)[0])
-    except IOError:
+    except OSError:
       pass
 
   try:
@@ -1053,7 +1053,7 @@ def tail(target: Union[str, BinaryIO], lines: Optional[int] = None) -> Iterator[
 
   :returns: **generator** that reads lines, starting with the end
 
-  :raises: **IOError** if unable to read the file
+  :raises: **OSError** if unable to read the file
   """
 
   if isinstance(target, str):
@@ -1163,7 +1163,7 @@ def is_tarfile(path: str) -> bool:
   # Checking if it's a tar file may fail due to permissions so failing back
   # to the mime type...
   #
-  #   IOError: [Errno 13] Permission denied: '/vmlinuz.old'
+  #   OSError: [Errno 13] Permission denied: '/vmlinuz.old'
   #
   # With python 3 insuffient permissions raises an AttributeError instead...
   #
@@ -1171,7 +1171,7 @@ def is_tarfile(path: str) -> bool:
 
   try:
     return tarfile.is_tarfile(path)
-  except (IOError, AttributeError):
+  except (OSError, AttributeError):
     return mimetypes.guess_type(path)[0] == 'application/x-tar'
 
 
@@ -1402,7 +1402,7 @@ def set_process_name(process_name: str) -> None:
 
   :param process_name: new name for our process
 
-  :raises: **IOError** if the process cannot be renamed
+  :raises: **OSError** if the process cannot be renamed
   """
 
   # This is mostly based on...
@@ -1448,7 +1448,7 @@ def _set_argv(process_name: str) -> None:
   Py_GetArgcArgv(argv, ctypes.pointer(argc))
 
   if len(process_name) > _MAX_NAME_LENGTH:
-    raise IOError("Can't rename process to something longer than our initial name (this would overwrite memory used for the env)")
+    raise OSError("Can't rename process to something longer than our initial name (this would overwrite memory used for the env)")
 
   # space we need to clear
   zero_size = max(len(current_name), len(process_name))
diff --git a/stem/version.py b/stem/version.py
index 9ed5fa37..cd2c3c39 100644
--- a/stem/version.py
+++ b/stem/version.py
@@ -60,7 +60,7 @@ def get_system_tor_version(tor_cmd: str = 'tor') -> 'stem.version.Version':
 
   :returns: :class:`~stem.version.Version` provided by the tor command
 
-  :raises: **IOError** if unable to query or parse the version
+  :raises: **OSError** if unable to query or parse the version
   """
 
   if tor_cmd not in VERSION_CACHE:
@@ -73,11 +73,11 @@ def get_system_tor_version(tor_cmd: str = 'tor') -> 'stem.version.Version':
 
       if 'No such file or directory' in str(exc):
         if os.path.isabs(tor_cmd):
-          raise IOError("Unable to check tor's version. '%s' doesn't exist." % tor_cmd)
+          raise OSError("Unable to check tor's version. '%s' doesn't exist." % tor_cmd)
         else:
-          raise IOError("Unable to run '%s'. Maybe tor isn't in your PATH?" % version_cmd)
+          raise OSError("Unable to run '%s'. Maybe tor isn't in your PATH?" % version_cmd)
 
-      raise IOError(exc)
+      raise OSError(exc)
 
     for line in version_output:
       # output example:
@@ -90,10 +90,10 @@ def get_system_tor_version(tor_cmd: str = 'tor') -> 'stem.version.Version':
           VERSION_CACHE[tor_cmd] = Version(version_str)
           break
         except ValueError as exc:
-          raise IOError(exc)
+          raise OSError(exc)
 
     if tor_cmd not in VERSION_CACHE:
-      raise IOError("'%s' didn't provide a parseable version:\n\n%s" % (version_cmd, '\n'.join(version_output)))
+      raise OSError("'%s' didn't provide a parseable version:\n\n%s" % (version_cmd, '\n'.join(version_output)))
 
   return VERSION_CACHE[tor_cmd]
 
diff --git a/test/integ/util/connection.py b/test/integ/util/connection.py
index 3e22667e..d2401aab 100644
--- a/test/integ/util/connection.py
+++ b/test/integ/util/connection.py
@@ -69,7 +69,7 @@ class TestConnection(unittest.TestCase):
   def test_connections_by_ss(self):
     try:
       self.check_resolver(Resolver.SS)
-    except (IOError, OSError):
+    except OSError:
       self.skipTest('(ticket 27479)')
 
   def test_connections_by_lsof(self):
diff --git a/test/integ/version.py b/test/integ/version.py
index 0df48646..1241fcfd 100644
--- a/test/integ/version.py
+++ b/test/integ/version.py
@@ -25,10 +25,10 @@ class TestVersion(unittest.TestCase):
     stem.version.get_system_tor_version()
 
     # try running against a command that exists, but isn't tor
-    self.assertRaises(IOError, stem.version.get_system_tor_version, 'ls')
+    self.assertRaises(OSError, stem.version.get_system_tor_version, 'ls')
 
     # try running against a command that doesn't exist
-    self.assertRaises(IOError, stem.version.get_system_tor_version, 'blarg')
+    self.assertRaises(OSError, stem.version.get_system_tor_version, 'blarg')
 
   @test.require.controller
   @async_test
diff --git a/test/unit/descriptor/collector.py b/test/unit/descriptor/collector.py
index 2960cf53..3f053a02 100644
--- a/test/unit/descriptor/collector.py
+++ b/test/unit/descriptor/collector.py
@@ -99,16 +99,16 @@ class TestCollector(unittest.TestCase):
 
   @patch('urllib.request.urlopen')
   def test_index_retries(self, urlopen_mock):
-    urlopen_mock.side_effect = IOError('boom')
+    urlopen_mock.side_effect = OSError('boom')
 
     collector = CollecTor(retries = 0)
-    self.assertRaisesRegexp(IOError, 'boom', collector.index)
+    self.assertRaisesRegexp(OSError, 'boom', collector.index)
     self.assertEqual(1, urlopen_mock.call_count)
 
     urlopen_mock.reset_mock()
 
     collector = CollecTor(retries = 4)
-    self.assertRaisesRegexp(IOError, 'boom', collector.index)
+    self.assertRaisesRegexp(OSError, 'boom', collector.index)
     self.assertEqual(5, urlopen_mock.call_count)
 
   @patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(b'not json')))
@@ -123,7 +123,7 @@ class TestCollector(unittest.TestCase):
 
       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)
+        self.assertRaisesRegexp(OSError, 'Failed to decompress as %s' % compression, collector.index, compression)
 
   @patch('stem.descriptor.collector.CollecTor.index', Mock(return_value = EXAMPLE_INDEX))
   def test_files(self):
diff --git a/test/unit/directory/fallback.py b/test/unit/directory/fallback.py
index 7b8f6da8..878218b8 100644
--- a/test/unit/directory/fallback.py
+++ b/test/unit/directory/fallback.py
@@ -117,11 +117,11 @@ class TestFallback(unittest.TestCase):
 
   @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)
+    self.assertRaisesRegexp(OSError, 'does not have a type field indicating it is fallback directory metadata', stem.directory.Fallback.from_remote)
 
   @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)
+    self.assertRaisesRegexp(OSError, 'Malformed fallback directory header line: /\\* version \\*/', stem.directory.Fallback.from_remote)
 
   def test_from_remote_malformed(self):
     test_values = {
@@ -135,7 +135,7 @@ class TestFallback(unittest.TestCase):
 
     for entry, expected in test_values.items():
       with patch('urllib.request.urlopen', Mock(return_value = io.BytesIO(entry))):
-        self.assertRaisesRegexp(IOError, re.escape(expected), stem.directory.Fallback.from_remote)
+        self.assertRaisesRegexp(OSError, re.escape(expected), stem.directory.Fallback.from_remote)
 
   def test_persistence(self):
     expected = {
diff --git a/test/unit/manual.py b/test/unit/manual.py
index 9d842972..d290f332 100644
--- a/test/unit/manual.py
+++ b/test/unit/manual.py
@@ -238,14 +238,14 @@ class TestManual(unittest.TestCase):
   @patch('stem.util.system.is_available', Mock(return_value = False))
   def test_download_man_page_requires_a2x(self):
     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')
+    self.assertRaisesWith(OSError, exc_msg, stem.manual.download_man_page, '/tmp/no_such_file')
 
   @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.manual.open', Mock(side_effect = OSError('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/man/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')
+    self.assertRaisesWith(OSError, exc_msg, stem.manual.download_man_page, '/tmp/no_such_file')
 
   @patch('tempfile.TemporaryDirectory', Mock(return_value = TEMP_DIR_MOCK))
   @patch('stem.manual.open', Mock(return_value = io.BytesIO()), create = True)
@@ -253,7 +253,7 @@ class TestManual(unittest.TestCase):
   @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')
+    self.assertRaisesWith(OSError, exc_msg, stem.manual.download_man_page, '/tmp/no_such_file', url = 'https://www.atagar.com/foo/bar')
 
   @patch('tempfile.TemporaryDirectory', Mock(return_value = TEMP_DIR_MOCK))
   @patch('stem.manual.open', Mock(return_value = io.BytesIO()), create = True)
@@ -262,7 +262,7 @@ class TestManual(unittest.TestCase):
   @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')
+    self.assertRaisesWith(OSError, exc_msg, stem.manual.download_man_page, '/tmp/no_such_file', url = 'https://www.atagar.com/foo/bar')
 
   @patch('tempfile.TemporaryDirectory', Mock(return_value = TEMP_DIR_MOCK))
   @patch('stem.manual.open', create = True)
@@ -290,7 +290,7 @@ class TestManual(unittest.TestCase):
   @patch('stem.util.system.call', Mock(side_effect = OSError('man --encoding=ascii -P cat tor returned exit status 16')))
   def test_from_man_when_manual_is_unavailable(self):
     exc_msg = "Unable to run 'man --encoding=ascii -P cat tor': man --encoding=ascii -P cat tor returned exit status 16"
-    self.assertRaisesWith(IOError, exc_msg, stem.manual.Manual.from_man)
+    self.assertRaisesWith(OSError, exc_msg, stem.manual.Manual.from_man)
 
   @patch('stem.util.system.call', Mock(return_value = []))
   def test_when_man_is_empty(self):
diff --git a/test/unit/util/connection.py b/test/unit/util/connection.py
index 848ce989..575667b4 100644
--- a/test/unit/util/connection.py
+++ b/test/unit/util/connection.py
@@ -181,12 +181,12 @@ class TestConnection(unittest.TestCase):
   def test_download_retries(self, urlopen_mock):
     urlopen_mock.side_effect = urllib.request.URLError('boom')
 
-    self.assertRaisesRegexp(IOError, 'boom', stem.util.connection.download, URL)
+    self.assertRaisesRegexp(OSError, 'boom', stem.util.connection.download, URL)
     self.assertEqual(1, urlopen_mock.call_count)
 
     urlopen_mock.reset_mock()
 
-    self.assertRaisesRegexp(IOError, 'boom', stem.util.connection.download, URL, retries = 4)
+    self.assertRaisesRegexp(OSError, 'boom', stem.util.connection.download, URL, retries = 4)
     self.assertEqual(5, urlopen_mock.call_count)
 
   @patch('os.access')
@@ -249,8 +249,8 @@ class TestConnection(unittest.TestCase):
 
     self.assertEqual(expected, stem.util.connection.get_connections(Resolver.PROC, process_pid = 1111))
 
-    proc_mock.side_effect = IOError('No connections for you!')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.PROC, process_pid = 1111)
+    proc_mock.side_effect = OSError('No connections for you!')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.PROC, process_pid = 1111)
 
   @patch('stem.util.system.call')
   def test_get_connections_by_netstat(self, call_mock):
@@ -262,11 +262,11 @@ class TestConnection(unittest.TestCase):
     expected = [Connection('192.168.0.1', 44284, '38.229.79.2', 443, 'tcp', False)]
     self.assertEqual(expected, stem.util.connection.get_connections(Resolver.NETSTAT, process_pid = 15843, process_name = 'tor'))
 
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.NETSTAT, process_pid = 15843, process_name = 'stuff')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.NETSTAT, process_pid = 1111, process_name = 'tor')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.NETSTAT, process_pid = 15843, process_name = 'stuff')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.NETSTAT, process_pid = 1111, process_name = 'tor')
 
     call_mock.side_effect = OSError('Unable to call netstat')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.NETSTAT, process_pid = 1111)
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.NETSTAT, process_pid = 1111)
 
   @patch('stem.util.system.call', Mock(return_value = NETSTAT_IPV6_OUTPUT.split('\n')))
   def test_get_connections_by_netstat_ipv6(self):
@@ -291,10 +291,10 @@ class TestConnection(unittest.TestCase):
     expected = [Connection('192.168.0.1', 44284, '38.229.79.2', 443, 'tcp', False)]
     self.assertEqual(expected, stem.util.connection.get_connections(Resolver.NETSTAT_WINDOWS, process_pid = 15843, process_name = 'tor'))
 
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.NETSTAT_WINDOWS, process_pid = 1111, process_name = 'tor')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.NETSTAT_WINDOWS, process_pid = 1111, process_name = 'tor')
     call_mock.side_effect = OSError('Unable to call netstat')
 
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.NETSTAT_WINDOWS, process_pid = 1111)
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.NETSTAT_WINDOWS, process_pid = 1111)
 
   @patch('stem.util.system.call')
   def test_get_connections_by_ss(self, call_mock):
@@ -309,11 +309,11 @@ class TestConnection(unittest.TestCase):
     ]
     self.assertEqual(expected, stem.util.connection.get_connections(Resolver.SS, process_pid = 15843, process_name = 'tor'))
 
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SS, process_pid = 15843, process_name = 'stuff')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SS, process_pid = 1111, process_name = 'tor')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.SS, process_pid = 15843, process_name = 'stuff')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.SS, process_pid = 1111, process_name = 'tor')
 
     call_mock.side_effect = OSError('Unable to call ss')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SS, process_pid = 1111)
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.SS, process_pid = 1111)
 
   @patch('stem.util.system.call', Mock(return_value = SS_IPV6_OUTPUT.split('\n')))
   def test_get_connections_by_ss_ipv6(self):
@@ -345,11 +345,11 @@ class TestConnection(unittest.TestCase):
     ]
     self.assertEqual(expected, stem.util.connection.get_connections(Resolver.LSOF, process_pid = 15843, process_name = 'tor'))
 
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.LSOF, process_pid = 15843, process_name = 'stuff')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.LSOF, process_pid = 1111, process_name = 'tor')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.LSOF, process_pid = 15843, process_name = 'stuff')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.LSOF, process_pid = 1111, process_name = 'tor')
 
     call_mock.side_effect = OSError('Unable to call lsof')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.LSOF, process_pid = 1111)
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.LSOF, process_pid = 1111)
 
   @patch('stem.util.system.call', Mock(return_value = LSOF_IPV6_OUTPUT.split('\n')))
   def test_get_connections_by_lsof_ipv6(self):
@@ -392,11 +392,11 @@ class TestConnection(unittest.TestCase):
     ]
     self.assertEqual(expected, stem.util.connection.get_connections(Resolver.BSD_SOCKSTAT, process_pid = 4397, process_name = 'tor'))
 
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_SOCKSTAT, process_pid = 4397, process_name = 'stuff')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_SOCKSTAT, process_pid = 1111, process_name = 'tor')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.BSD_SOCKSTAT, process_pid = 4397, process_name = 'stuff')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.BSD_SOCKSTAT, process_pid = 1111, process_name = 'tor')
 
     call_mock.side_effect = OSError('Unable to call sockstat')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_SOCKSTAT, process_pid = 1111)
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.BSD_SOCKSTAT, process_pid = 1111)
 
   @patch('stem.util.system.call')
   def test_get_connections_by_procstat(self, call_mock):
@@ -413,11 +413,11 @@ class TestConnection(unittest.TestCase):
     ]
     self.assertEqual(expected, stem.util.connection.get_connections(Resolver.BSD_PROCSTAT, process_pid = 3561, process_name = 'tor'))
 
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_PROCSTAT, process_pid = 3561, process_name = 'stuff')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_PROCSTAT, process_pid = 1111, process_name = 'tor')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.BSD_PROCSTAT, process_pid = 3561, process_name = 'stuff')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.BSD_PROCSTAT, process_pid = 1111, process_name = 'tor')
 
     call_mock.side_effect = OSError('Unable to call procstat')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_PROCSTAT, process_pid = 1111)
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.BSD_PROCSTAT, process_pid = 1111)
 
   @patch('stem.util.system.call')
   def test_get_connections_by_fstat(self, call_mock):
@@ -432,11 +432,11 @@ class TestConnection(unittest.TestCase):
     ]
     self.assertEqual(expected, stem.util.connection.get_connections(Resolver.BSD_FSTAT, process_pid = 15843, process_name = 'tor'))
 
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_FSTAT, process_pid = 15843, process_name = 'stuff')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_FSTAT, process_pid = 1111, process_name = 'tor')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.BSD_FSTAT, process_pid = 15843, process_name = 'stuff')
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.BSD_FSTAT, process_pid = 1111, process_name = 'tor')
 
     call_mock.side_effect = OSError('Unable to call fstat')
-    self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.BSD_FSTAT, process_pid = 1111)
+    self.assertRaises(OSError, stem.util.connection.get_connections, Resolver.BSD_FSTAT, process_pid = 1111)
 
   def test_is_valid_ipv4_address(self):
     """
diff --git a/test/unit/util/proc.py b/test/unit/util/proc.py
index b5667c62..31fd742c 100644
--- a/test/unit/util/proc.py
+++ b/test/unit/util/proc.py
@@ -174,7 +174,7 @@ class TestProc(unittest.TestCase):
     # check that we reject bad pids
 
     for arg in (None, -100, 'hello',):
-      self.assertRaises(IOError, proc.file_descriptors_used, arg)
+      self.assertRaises(OSError, proc.file_descriptors_used, arg)
 
     # when proc directory doesn't exist
 
@@ -182,7 +182,7 @@ class TestProc(unittest.TestCase):
     listdir_mock.side_effect = OSError(error_msg)
 
     exc_msg = 'Unable to check number of file descriptors used: %s' % error_msg
-    self.assertRaisesWith(IOError, exc_msg, proc.file_descriptors_used, 2118)
+    self.assertRaisesWith(OSError, exc_msg, proc.file_descriptors_used, 2118)
 
     # successful calls
 
diff --git a/test/unit/util/system.py b/test/unit/util/system.py
index 042c1bd1..fe501389 100644
--- a/test/unit/util/system.py
+++ b/test/unit/util/system.py
@@ -402,11 +402,11 @@ class TestSystem(unittest.TestCase):
     self.assertEqual(14, len(list(system.tail(path))))
     self.assertEqual(14, len(list(system.tail(path, 200))))
 
-    self.assertRaises(IOError, list, system.tail('/path/doesnt/exist'))
+    self.assertRaises(OSError, list, system.tail('/path/doesnt/exist'))
 
     fd, temp_path = tempfile.mkstemp()
     os.chmod(temp_path, 0o077)  # remove read permissions
-    self.assertRaises(IOError, list, system.tail(temp_path))
+    self.assertRaises(OSError, list, system.tail(temp_path))
     os.close(fd)
     os.remove(temp_path)
 
diff --git a/test/unit/version.py b/test/unit/version.py
index 33439cf8..70109aca 100644
--- a/test/unit/version.py
+++ b/test/unit/version.py
@@ -58,7 +58,7 @@ class TestVersion(unittest.TestCase):
     Tor version output that doesn't include a version within it.
     """
 
-    self.assertRaisesRegexp(IOError, "'tor_unit --version' didn't provide a parseable version", stem.version.get_system_tor_version, 'tor_unit')
+    self.assertRaisesRegexp(OSError, "'tor_unit --version' didn't provide a parseable version", stem.version.get_system_tor_version, 'tor_unit')
 
   @patch('stem.util.system.call', Mock(return_value = MALFORMED_TOR_VERSION.splitlines()))
   @patch.dict(stem.version.VERSION_CACHE)
@@ -68,7 +68,7 @@ class TestVersion(unittest.TestCase):
     version.
     """
 
-    self.assertRaisesWith(IOError, "'0.2.blah (git-73ff13ab3cc9570d)' isn't a properly formatted tor version", stem.version.get_system_tor_version, 'tor_unit')
+    self.assertRaisesWith(OSError, "'0.2.blah (git-73ff13ab3cc9570d)' isn't a properly formatted tor version", stem.version.get_system_tor_version, 'tor_unit')
 
   def test_parsing(self):
     """



More information about the tor-commits mailing list