← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:new-oserror into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:new-oserror into launchpad:master.

Commit message:
Use new OSError exception hierarchy in Python 3

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/429983

This is generally less confusing than the catch-and-maybe-reraise idioms we were previously using.

In one case the new code isn't precisely equivalent to the old code: `lp.services.osutils.two_stage_kill` no longer suppresses `OSError` exceptions other than the ones it explicitly catches (which should only be possible here in the case of programming errors).  I think this was probably a mistake in the previous code, since it's exactly the kind of thing that's easy to do by mistake when using catch-and-maybe-reraise idioms.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:new-oserror into launchpad:master.
diff --git a/lib/lp/app/browser/folder.py b/lib/lp/app/browser/folder.py
index ad4c620..ca68a88 100644
--- a/lib/lp/app/browser/folder.py
+++ b/lib/lp/app/browser/folder.py
@@ -6,7 +6,6 @@ __all__ = [
     "ExportedImageFolder",
 ]
 
-import errno
 import os
 import re
 import time
@@ -86,14 +85,8 @@ class ExportedFolder:
         name = os.path.basename(filename)
         try:
             fileobj = File(filename, name)
-        except OSError as ioerror:
-            expected = (errno.ENOENT, errno.EISDIR, errno.ENOTDIR)
-            if ioerror.errno in expected:
-                # No such file or is a directory.
-                raise NotFound(self, name)
-            else:
-                # Some other IOError that we're not expecting.
-                raise
+        except (FileNotFoundError, IsADirectoryError, NotADirectoryError):
+            raise NotFound(self, name)
 
         # TODO: Set an appropriate charset too.  There may be zope code we
         #       can reuse for this.
diff --git a/lib/lp/archivepublisher/publishing.py b/lib/lp/archivepublisher/publishing.py
index 87a78b9..f714c77 100644
--- a/lib/lp/archivepublisher/publishing.py
+++ b/lib/lp/archivepublisher/publishing.py
@@ -11,7 +11,6 @@ __all__ = [
 ]
 
 import bz2
-import errno
 import gzip
 import hashlib
 import lzma
@@ -1377,9 +1376,8 @@ class Publisher:
                         )
                         extra_files.add(remove_suffix(dep11_path))
                         extra_files.add(dep11_path)
-            except OSError as e:
-                if e.errno != errno.ENOENT:
-                    raise
+            except FileNotFoundError:
+                pass
             cnf_dir = os.path.join(suite_dir, component, "cnf")
             try:
                 for cnf_file in os.listdir(cnf_dir):
@@ -1387,9 +1385,8 @@ class Publisher:
                         cnf_path = os.path.join(component, "cnf", cnf_file)
                         extra_files.add(remove_suffix(cnf_path))
                         extra_files.add(cnf_path)
-            except OSError as e:
-                if e.errno != errno.ENOENT:
-                    raise
+            except FileNotFoundError:
+                pass
         for architecture in all_architectures:
             for contents_path in get_suffixed_indices(
                 "Contents-" + architecture
@@ -1577,9 +1574,8 @@ class Publisher:
                     continue
                 i18n_files.add(remove_suffix(entry.name))
                 i18n_files.add(entry.name)
-        except OSError as e:
-            if e.errno != errno.ENOENT:
-                raise
+        except FileNotFoundError:
+            pass
         if not i18n_files:
             # If the i18n directory doesn't exist or is empty, we don't need
             # to index it.
diff --git a/lib/lp/archiveuploader/dscfile.py b/lib/lp/archiveuploader/dscfile.py
index d16de4c..c2e7941 100644
--- a/lib/lp/archiveuploader/dscfile.py
+++ b/lib/lp/archiveuploader/dscfile.py
@@ -16,7 +16,6 @@ __all__ = [
     "SignableTagFile",
 ]
 
-import errno
 import glob
 import io
 import os
@@ -95,17 +94,15 @@ def cleanup_unpacked_dir(unpacked_dir):
     """
     try:
         shutil.rmtree(unpacked_dir)
+    except PermissionError:
+        result = os.system("chmod -R u+rwx " + unpacked_dir)
+        if result != 0:
+            raise UploadError("chmod failed with %s" % result)
+        shutil.rmtree(unpacked_dir)
     except OSError as error:
-        if errno.errorcode[error.errno] != "EACCES":
-            raise UploadError(
-                "couldn't remove tmp dir %s: code %s"
-                % (unpacked_dir, error.errno)
-            )
-        else:
-            result = os.system("chmod -R u+rwx " + unpacked_dir)
-            if result != 0:
-                raise UploadError("chmod failed with %s" % result)
-            shutil.rmtree(unpacked_dir)
+        raise UploadError(
+            "couldn't remove tmp dir %s: code %s" % (unpacked_dir, error.errno)
+        )
 
 
 class SignableTagFile:
diff --git a/lib/lp/bugs/externalbugtracker/tests/test_debbugs.py b/lib/lp/bugs/externalbugtracker/tests/test_debbugs.py
index f019c72..dd42606 100644
--- a/lib/lp/bugs/externalbugtracker/tests/test_debbugs.py
+++ b/lib/lp/bugs/externalbugtracker/tests/test_debbugs.py
@@ -4,7 +4,6 @@
 """Tests for the Debian bugtracker"""
 
 import email.message
-import errno
 import os
 
 from testtools.matchers import Equals, IsInstance, raises
@@ -30,9 +29,8 @@ class TestDebBugs(TestCase):
         bucket = os.path.join(self.tempdir, "db-h", bucket_name)
         try:
             os.makedirs(bucket)
-        except OSError as e:
-            if e.errno != errno.EEXIST:
-                raise
+        except FileExistsError:
+            pass
         m = email.message.Message()
         # For whatever reason Date is a unix timestamp not an email datestamp
         m["Date"] = "1000000000"
diff --git a/lib/lp/bugs/scripts/debbugs.py b/lib/lp/bugs/scripts/debbugs.py
index fc85e34..a0307fc 100644
--- a/lib/lp/bugs/scripts/debbugs.py
+++ b/lib/lp/bugs/scripts/debbugs.py
@@ -177,10 +177,8 @@ class Database:
 
         try:
             fd = open(summary)
-        except OSError as e:
-            if e.errno == 2:
-                raise SummaryMissing(summary)
-            raise
+        except FileNotFoundError:
+            raise SummaryMissing(summary)
 
         try:
             message = email.message_from_file(fd)
@@ -222,10 +220,8 @@ class Database:
 
         try:
             fd = open(report, "rb")
-        except OSError as e:
-            if e.errno == 2:
-                raise ReportMissing(report)
-            raise
+        except FileNotFoundError:
+            raise ReportMissing(report)
 
         bug.report = fd.read()
         fd.close()
@@ -278,10 +274,8 @@ class Database:
             if process.returncode != 0:
                 raise LogParseFailed(errors)
 
-        except OSError as e:
-            if e.errno == 2:
-                raise LogMissing(log)
-            raise
+        except FileNotFoundError:
+            raise LogMissing(log)
 
         bug.comments = comments
 
diff --git a/lib/lp/codehosting/sftp.py b/lib/lp/codehosting/sftp.py
index 728bb3e..fc2d4e8 100644
--- a/lib/lp/codehosting/sftp.py
+++ b/lib/lp/codehosting/sftp.py
@@ -18,7 +18,6 @@ __all__ = [
 ]
 
 
-import errno
 import os
 import stat
 from copy import copy
@@ -58,9 +57,7 @@ class FatLocalTransport(LocalTransport):
         osutils.check_legal_path(abspath)
         try:
             chunk_file = os.open(abspath, os.O_CREAT | os.O_WRONLY)
-        except OSError as e:
-            if e.errno != errno.EISDIR:
-                raise
+        except IsADirectoryError:
             raise FileIsADirectory(name)
         os.lseek(chunk_file, offset, 0)
         os.write(chunk_file, data)
diff --git a/lib/lp/registry/tests/test_mlists.py b/lib/lp/registry/tests/test_mlists.py
index ad68186..42fef42 100644
--- a/lib/lp/registry/tests/test_mlists.py
+++ b/lib/lp/registry/tests/test_mlists.py
@@ -3,7 +3,6 @@
 
 """Test mailing list stuff."""
 
-import errno
 import os
 import tempfile
 import unittest
@@ -58,9 +57,8 @@ class BaseMailingListImportTest(unittest.TestCase):
     def tearDown(self):
         try:
             os.remove(self.filename)
-        except OSError as error:
-            if error.errno != errno.ENOENT:
-                raise
+        except FileNotFoundError:
+            pass
 
     def _makeList(self, name, owner):
         self.team, self.mailing_list = factory.makeTeamAndMailingList(
diff --git a/lib/lp/services/command_spawner.py b/lib/lp/services/command_spawner.py
index 8bf3ccc..fd3a74a 100644
--- a/lib/lp/services/command_spawner.py
+++ b/lib/lp/services/command_spawner.py
@@ -9,7 +9,6 @@ __all__ = [
     "ReturnCodeReceiver",
 ]
 
-import errno
 import select
 import subprocess
 from fcntl import F_GETFL, F_SETFL, fcntl
@@ -175,11 +174,9 @@ class CommandSpawner:
         """Read output from `pipe_file`."""
         try:
             output = pipe_file.read()
-        except OSError as e:
-            # "Resource temporarily unavailable"--not an error really,
-            # just means there's nothing to read.
-            if e.errno != errno.EAGAIN:
-                raise
+        except BlockingIOError:
+            # This just means there's nothing to read.
+            pass
         else:
             if len(output) > 0:
                 self._handle(process, event, output)
diff --git a/lib/lp/services/doc/pidfile.rst b/lib/lp/services/doc/pidfile.rst
index 77e8000..70eabb3 100644
--- a/lib/lp/services/doc/pidfile.rst
+++ b/lib/lp/services/doc/pidfile.rst
@@ -80,7 +80,6 @@ The pidfile must also be removed if the process is exited with SIGINT (Ctrl-C)
 or SIGTERM, too. We'll demonstrate this with a couple of functions, because
 we'll need them again later.
 
-    >>> import errno
     >>> import time
     >>> subprocess_code = '''
     ... import time
@@ -111,11 +110,8 @@ we'll need them again later.
     ...         try:
     ...             # Is it still here at all?
     ...             os.kill(pid, 0)
-    ...         except OSError as e:
-    ...             if e.errno == errno.ESRCH:
-    ...                 print("Error: pid file was not removed")
-    ...             else:
-    ...                 raise
+    ...         except ProcessLookupError:
+    ...             print("Error: pid file was not removed")
     ...         else:
     ...             print("Error: process did not exit")
     ...
diff --git a/lib/lp/services/librarianserver/librariangc.py b/lib/lp/services/librarianserver/librariangc.py
index 0ab69ad..a0ec1ad 100644
--- a/lib/lp/services/librarianserver/librariangc.py
+++ b/lib/lp/services/librarianserver/librariangc.py
@@ -3,7 +3,6 @@
 
 """Librarian garbage collection routines"""
 
-import errno
 import hashlib
 import multiprocessing.pool
 import os
@@ -589,9 +588,8 @@ class UnreferencedContentPruner:
         try:
             os.unlink(path)
             removed.append("filesystem")
-        except OSError as e:
-            if e.errno != errno.ENOENT:
-                raise
+        except FileNotFoundError:
+            pass
 
         # Remove the file from Swift, if it hasn't already been.
         if self.swift_enabled:
diff --git a/lib/lp/services/librarianserver/storage.py b/lib/lp/services/librarianserver/storage.py
index 0117f2e..2c2437b 100644
--- a/lib/lp/services/librarianserver/storage.py
+++ b/lib/lp/services/librarianserver/storage.py
@@ -1,7 +1,6 @@
 # Copyright 2009-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-import errno
 import hashlib
 import os
 import shutil
@@ -56,9 +55,8 @@ def makedirs_fsync(name, mode=0o777):
     if head and tail and not os.path.exists(head):
         try:
             makedirs_fsync(head, mode)
-        except OSError as e:
-            if e.errno != errno.EEXIST:
-                raise
+        except FileExistsError:
+            pass
         if tail == os.curdir:
             return
     os.mkdir(name, mode)
@@ -100,9 +98,8 @@ class LibrarianStorage:
         self.incoming = os.path.join(self.directory, "incoming")
         try:
             os.mkdir(self.incoming)
-        except OSError as e:
-            if e.errno != errno.EEXIST:
-                raise
+        except FileExistsError:
+            pass
 
     def hasFile(self, fileid):
         return os.access(self._fileLocation(fileid), os.F_OK)
@@ -321,10 +318,9 @@ class LibraryFileUpload:
             raise DuplicateFileIDError(fileID)
         try:
             makedirs_fsync(os.path.dirname(location))
-        except OSError as e:
+        except FileExistsError:
             # If the directory already exists, that's ok.
-            if e.errno != errno.EEXIST:
-                raise
+            pass
         shutil.move(self.tmpfilepath, location)
         fsync_path(location)
         fsync_path(os.path.dirname(location), dir=True)
diff --git a/lib/lp/services/osutils.py b/lib/lp/services/osutils.py
index 911e173..1a2e78a 100644
--- a/lib/lp/services/osutils.py
+++ b/lib/lp/services/osutils.py
@@ -16,7 +16,6 @@ __all__ = [
     "write_file",
 ]
 
-import errno
 import os.path
 import shutil
 import time
@@ -67,10 +66,8 @@ def ensure_directory_exists(directory, mode=0o777):
     """
     try:
         os.makedirs(directory, mode=mode)
-    except OSError as e:
-        if e.errno == errno.EEXIST:
-            return False
-        raise
+    except FileExistsError:
+        return False
     return True
 
 
@@ -87,23 +84,18 @@ def open_for_writing(filename, mode, dirmode=0o777):
     """
     try:
         return open(filename, mode)
-    except OSError as e:
-        if e.errno == errno.ENOENT:
-            os.makedirs(os.path.dirname(filename), mode=dirmode)
-            return open(filename, mode)
-        raise
+    except FileNotFoundError:
+        os.makedirs(os.path.dirname(filename), mode=dirmode)
+        return open(filename, mode)
 
 
 def _kill_may_race(pid, signal_number):
     """Kill a pid accepting that it may not exist."""
     try:
         os.kill(pid, signal_number)
-    except OSError as e:
-        if e.errno in (errno.ESRCH, errno.ECHILD):
-            # Process has already been killed.
-            return
-        # Some other issue (e.g. different user owns it)
-        raise
+    except (ProcessLookupError, ChildProcessError):
+        # Process has already been killed.
+        return
 
 
 def two_stage_kill(pid, poll_interval=0.1, num_polls=50, get_status=True):
@@ -133,11 +125,10 @@ def two_stage_kill(pid, poll_interval=0.1, num_polls=50, get_status=True):
                 if not process_exists(pid):
                     return
             time.sleep(poll_interval)
-        except OSError as e:
-            if e.errno in (errno.ESRCH, errno.ECHILD):
-                # Raised if the process is gone by the time we try to get the
-                # return value.
-                return
+        except (ProcessLookupError, ChildProcessError):
+            # Raised if the process is gone by the time we try to get the
+            # return value.
+            return
 
     # The process is still around, so terminate it violently.
     _kill_may_race(pid, SIGKILL)
@@ -176,9 +167,8 @@ def remove_if_exists(path):
     """Remove the given file if it exists."""
     try:
         os.remove(path)
-    except OSError as e:
-        if e.errno != errno.ENOENT:
-            raise
+    except FileNotFoundError:
+        pass
 
 
 def write_file(path, content):
@@ -190,11 +180,7 @@ def process_exists(pid):
     """Return True if the specified process already exists."""
     try:
         os.kill(pid, 0)
-    except os.error as err:
-        if err.errno == errno.ESRCH:
-            # All is well - the process doesn't exist.
-            return False
-        else:
-            # We got a strange OSError, which we'll pass upwards.
-            raise
+    except ProcessLookupError:
+        # All is well - the process doesn't exist.
+        return False
     return True
diff --git a/lib/lp/services/sitesearch/tests/bingserviceharness.py b/lib/lp/services/sitesearch/tests/bingserviceharness.py
index 68cb697..4e3fe98 100644
--- a/lib/lp/services/sitesearch/tests/bingserviceharness.py
+++ b/lib/lp/services/sitesearch/tests/bingserviceharness.py
@@ -7,8 +7,6 @@ Fixtures for running the Bing test webservice.
 
 __all__ = ["BingServiceTestSetup"]
 
-
-import errno
 import os
 import signal
 
@@ -95,11 +93,10 @@ class BingServiceTestSetup:
         if cls.service:
             try:
                 os.kill(cls.service.pid, signal.SIGTERM)
-            except OSError as error:
-                if error.errno != errno.ESRCH:
-                    raise
+            except ProcessLookupError:
                 # The process with the given pid doesn't exist, so there's
                 # nothing to kill or wait for.
+                pass
             else:
                 cls.service.wait()
         cls.service = None
diff --git a/lib/lp/services/sitesearch/testservice.py b/lib/lp/services/sitesearch/testservice.py
index 7384b03..c602f5d 100644
--- a/lib/lp/services/sitesearch/testservice.py
+++ b/lib/lp/services/sitesearch/testservice.py
@@ -8,8 +8,6 @@ This script runs a simple HTTP server. The server returns files
 when given certain user-configurable URLs.
 """
 
-
-import errno
 import os
 import signal
 import socket
@@ -111,13 +109,10 @@ def wait_for_service(host, port, timeout=15.0):
         while True:
             try:
                 sock.connect((host, port))
-            except OSError as err:
-                if err.args[0] in [errno.ECONNREFUSED, errno.ECONNABORTED]:
-                    elapsed = time.time() - start
-                    if elapsed > timeout:
-                        raise RuntimeError("Socket poll time exceeded.")
-                else:
-                    raise
+            except (ConnectionRefusedError, ConnectionAbortedError):
+                elapsed = time.time() - start
+                if elapsed > timeout:
+                    raise RuntimeError("Socket poll time exceeded.")
             else:
                 break
             time.sleep(0.1)
@@ -143,12 +138,9 @@ def wait_for_service_shutdown(host, port, seconds_to_wait=10.0):
             try:
                 sock.connect((host, port))
                 sock.close()
-            except OSError as err:
-                if err.args[0] == errno.ECONNREFUSED:
-                    # Success!  The socket is closed.
-                    return
-                else:
-                    raise
+            except ConnectionRefusedError:
+                # Success!  The socket is closed.
+                return
             else:
                 elapsed = time.time() - start
                 if elapsed > seconds_to_wait:
@@ -215,12 +207,8 @@ def kill_running_process(service_name, host, port):
                 # between freeing the socket in the killed process, and
                 # opening it in the current one.
                 wait_for_service_shutdown(host, port)
-            except os.error as err:
-                if err.errno == errno.ESRCH:
-                    # Whoops, we got a 'No such process' error. The PID file
-                    # is probably stale, so we'll remove it to prevent trash
-                    # from lying around in the test environment.
-                    # See bug #237086.
-                    remove_if_exists(pidfile_path(service_name))
-                else:
-                    raise
+            except ProcessLookupError:
+                # The PID file is probably stale, so we'll remove it to
+                # prevent trash from lying around in the test environment.
+                # See bug #237086.
+                remove_if_exists(pidfile_path(service_name))
diff --git a/lib/lp/services/tests/test_osutils.py b/lib/lp/services/tests/test_osutils.py
index d344dfe..c576c21 100644
--- a/lib/lp/services/tests/test_osutils.py
+++ b/lib/lp/services/tests/test_osutils.py
@@ -118,9 +118,7 @@ class TestProcessExists(TestCase):
         self.assertTrue(process_exists(pid))
 
     def test_with_process_not_running(self):
-        exception = OSError()
-        exception.errno = errno.ESRCH
-        self.useFixture(MockPatch("os.kill", side_effect=exception))
+        self.useFixture(MockPatch("os.kill", side_effect=ProcessLookupError))
         self.assertFalse(process_exists(123))
 
     def test_with_unknown_error(self):
diff --git a/lib/lp/soyuz/tests/test_packagediff.py b/lib/lp/soyuz/tests/test_packagediff.py
index 4c0c48b..d87ddaf 100644
--- a/lib/lp/soyuz/tests/test_packagediff.py
+++ b/lib/lp/soyuz/tests/test_packagediff.py
@@ -3,7 +3,6 @@
 
 """Test source package diffs."""
 
-import errno
 import os.path
 from datetime import datetime
 from textwrap import dedent
@@ -202,8 +201,7 @@ class TestPackageDiffs(TestCaseWithFactory):
         with open(marker_path) as marker:
             debdiff_pid = int(marker.readline())
             debdiff_tmpdir = marker.readline().rstrip("\n")
-            err = self.assertRaises(OSError, os.kill, debdiff_pid, 0)
-            self.assertEqual(errno.ESRCH, err.errno)
+            self.assertRaises(ProcessLookupError, os.kill, debdiff_pid, 0)
             self.assertFalse(os.path.exists(debdiff_tmpdir))
 
     def test_packagediff_max_size(self):
@@ -235,8 +233,7 @@ class TestPackageDiffs(TestCaseWithFactory):
         with open(marker_path) as marker:
             debdiff_pid = int(marker.readline())
             debdiff_tmpdir = marker.readline().rstrip("\n")
-            err = self.assertRaises(OSError, os.kill, debdiff_pid, 0)
-            self.assertEqual(errno.ESRCH, err.errno)
+            self.assertRaises(ProcessLookupError, os.kill, debdiff_pid, 0)
             self.assertFalse(os.path.exists(debdiff_tmpdir))
 
     def test_packagediff_blacklist(self):
diff --git a/lib/lp/testing/layers.py b/lib/lp/testing/layers.py
index 0777457..a6fae41 100644
--- a/lib/lp/testing/layers.py
+++ b/lib/lp/testing/layers.py
@@ -47,7 +47,6 @@ __all__ = [
 
 import base64
 import datetime
-import errno
 import gc
 import os
 import select
@@ -1687,15 +1686,11 @@ class LayerProcessController:
         """
         try:
             os.kill(cls.appserver.pid, sig)
-        except OSError as error:
-            if error.errno == errno.ESRCH:
-                # The child process doesn't exist.  Maybe it went away by the
-                # time we got here.
-                cls.appserver = None
-                return False
-            else:
-                # Something else went wrong.
-                raise
+        except ProcessLookupError:
+            # The child process doesn't exist.  Maybe it went away by the
+            # time we got here.
+            cls.appserver = None
+            return False
         else:
             return True
 
@@ -1759,9 +1754,8 @@ class LayerProcessController:
             # Don't worry if the process no longer exists.
             try:
                 os.kill(pid, signal.SIGTERM)
-            except OSError as error:
-                if error.errno != errno.ESRCH:
-                    raise
+            except ProcessLookupError:
+                pass
             pidfile.remove_pidfile("launchpad", cls.appserver_config)
 
     @classmethod
@@ -1801,10 +1795,8 @@ class LayerProcessController:
                         % error.code
                     )
             except URLError as error:
-                # We are interested in a wrapped socket.error.
-                if not isinstance(error.reason, socket.error):
-                    raise
-                if error.reason.args[0] != errno.ECONNREFUSED:
+                # We are interested in a wrapped ConnectionRefusedError.
+                if not isinstance(error.reason, ConnectionRefusedError):
                     raise
                 returncode = cls.appserver.poll()
                 if returncode is not None:
diff --git a/lib/lp/translations/pottery/tests/test_detect_intltool.py b/lib/lp/translations/pottery/tests/test_detect_intltool.py
index cbf90d2..c25078f 100644
--- a/lib/lp/translations/pottery/tests/test_detect_intltool.py
+++ b/lib/lp/translations/pottery/tests/test_detect_intltool.py
@@ -1,7 +1,6 @@
 # Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-import errno
 import os
 import tarfile
 from textwrap import dedent
@@ -48,10 +47,9 @@ class SetupTestPackageMixin:
             if directory != "":
                 try:
                     os.makedirs(directory)
-                except OSError as e:
+                except FileExistsError:
                     # Doesn't matter if it already exists.
-                    if e.errno != errno.EEXIST:
-                        raise
+                    pass
             with open(path, "w") as the_file:
                 the_file.write(content)
 
diff --git a/test_on_merge.py b/test_on_merge.py
index 63087f0..99f343d 100755
--- a/test_on_merge.py
+++ b/test_on_merge.py
@@ -7,7 +7,6 @@
 
 import _pythonpath  # noqa: F401
 
-import errno
 import os
 import select
 import sys
@@ -241,12 +240,9 @@ def nice_killpg(pgid):
             # Give the processes some time to shut down.
             time.sleep(3)
 
-    except OSError as exc:
-        if exc.errno == errno.ESRCH:
-            # We tried to call os.killpg() and found the group to be empty.
-            pass
-        else:
-            raise
+    except ProcessLookupError:
+        # We tried to call os.killpg() and found the group to be empty.
+        pass
     print("Process group %d is now empty." % pgid)