Repository: giampaolo/psutil
Pull request #2096 (2022-04-11 21:59:35)

[Linux] `net_if_stats()` returns an incorrect speed value for 100GbE network cards
## Summary

* OS: Linux
* Bug fix: Yes
* Type: Core
* Fixes: #2095

## Description

Properly handle the speed variable from `ethtool.h` to support interface speed that are greater than UINT16_MAX. Since ethtool returns the interface speed as two separate `uint16_t` values, in order to determine the actual interface speed, you must combine and bit-shift the two values.

## Testing

All testing done on Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-100-generic x86_64) with Python 3.8.10.

System with 100GbE network interface up (`eth2`). Confirmed that interfaces that are down show a speed of 0, and the 100GbE interface that is up properly returns 100,000 Mb/s.
```python
>>> import psutil
>>> psutil.net_if_stats()
{'eth1': snicstats(isup=False, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=1500), 'eth0': snicstats(isup=False, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=1500), 'eth2': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100000, mtu=1500), 'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536)}
>>>
```

System with 10GbE network interface up (`eth1`)
```python
>>> import psutil
>>> psutil.net_if_stats()
{'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536), 'eth0': snicstats(isup=False, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=1500), 'eth1': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=10000, mtu=1500)}
>>>
``` 


Patch:
diff --git a/.flake8 b/.flake8
index 1244cd48..8be0bfcf 100644
--- a/.flake8
+++ b/.flake8
@@ -1,15 +1,28 @@
 # Configuration file for flake 8. This is used by "make lint" and by the
 # GIT commit hook script.
-# T001 = print() statement
 
 [flake8]
 ignore =
-    # line break after binary operator
-    W504
+    W504  # line break after binary operator
+
+    # --- flake8-bugbear plugin
+    B007  # Loop control variable 'keyword' not used within the loop body. If this is intended, start the name with an underscore.
+    B014  # Redundant exception types in `except (IOError, OSError) as err:`.  Write `except OSError as err:`, which catches exactly the same exceptions.
+    B008  # Do not perform function calls in argument defaults.
+
+    # --- flake8-blind-except plugin
+    B902  # blind except Exception: statement
+
+    # --- flake8-quotes plugin
+    Q000  # Double quotes found but single quotes preferred
+
+    # --- flake8-quotes naming; disable all except N804 and N805
+    N801, N802, N803, N806, N807, N811, N812, N813, N814, N815, N816, N817, N818
+
 per-file-ignores =
-   setup.py:T001
-   scripts/*:T001
-   scripts/internal/convert_readme.py:E501,T001
-   psutil/tests/runner.py:T001
-   psutil/tests/test_memleaks.py:T001
-   .github/workflows/*:T001
+    # T001, T201 = print() statement (flake8-print plugin)
+    setup.py:T001,T201
+    scripts/*:T001,T201
+    psutil/tests/runner.py:T001,T201
+    psutil/tests/test_memleaks.py:T001,T201
+    .github/workflows/*:T001,T201
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f9c0a435..7a592a32 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -46,10 +46,12 @@ jobs:
       with:
         access_token: ${{ github.token }}
 
-    - uses: actions/checkout@v2
-    - uses: actions/setup-python@v2
+    - uses: actions/checkout@v3
+    - uses: actions/setup-python@v3
       with:
         python-version: 3.9
+        cache: pip
+        cache-dependency-path: .github/workflows/build.yml
 
     - name: Install cibuildwheel
       run: pip install cibuildwheel
@@ -63,7 +65,7 @@ jobs:
       run: cibuildwheel .
 
     - name: Create wheels
-      uses: actions/upload-artifact@v2
+      uses: actions/upload-artifact@v3
       with:
         name: wheels
         path: wheelhouse
@@ -102,10 +104,12 @@ jobs:
       with:
         access_token: ${{ github.token }}
 
-    - uses: actions/checkout@v2
-    - uses: actions/setup-python@v2
+    - uses: actions/checkout@v3
+    - uses: actions/setup-python@v3
       with:
         python-version: 3.9
+        cache: pip
+        cache-dependency-path: .github/workflows/build.yml
 
     - name: Install cibuildwheel
       run: pip install cibuildwheel==1.12.0
@@ -114,7 +118,7 @@ jobs:
       run: cibuildwheel .
 
     - name: Create wheels
-      uses: actions/upload-artifact@v2
+      uses: actions/upload-artifact@v3
       with:
         name: wheels
         path: wheelhouse
@@ -135,7 +139,7 @@ jobs:
       with:
         access_token: ${{ github.token }}
 
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: Run tests
       id: test
@@ -157,8 +161,11 @@ jobs:
   linters:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-python@v2
+    - uses: actions/checkout@v3
+    - uses: actions/setup-python@v3
+      with:
+        cache: pip
+        cache-dependency-path: .github/workflows/build.yml
     - name: 'Run linters'
       run: |
         # py2
diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml
index fa739eab..d5118157 100644
--- a/.github/workflows/issues.yml
+++ b/.github/workflows/issues.yml
@@ -12,11 +12,13 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     # install python
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - name: Install Python
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v3
       with:
         python-version: 3.8
+        cache: pip
+        cache-dependency-path: .github/workflows/issues.yml
     # install deps
     - name: Install deps
       run: python -m pip install --upgrade pip PyGithub
diff --git a/CREDITS b/CREDITS
index 131bbfe9..61e75453 100644
--- a/CREDITS
+++ b/CREDITS
@@ -778,3 +778,14 @@ I: 1980
 N: Olivier Dormond
 W: https://github.com/odormond
 I: 1956
+
+N: Matthieu Darbois
+W: https://github.com/mayeut
+I: 2039
+
+N: Hugo van Kemenade
+W: https://github.com/hugovk
+I: 2099
+
+N: Torsten Blum
+I: 2114
diff --git a/HISTORY.rst b/HISTORY.rst
index 595de975..d4c68192 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -1,25 +1,37 @@
 *Bug tracker at https://github.com/giampaolo/psutil/issues*
 
-5.9.1 (IN DEVELOPMENT)
-======================
+**Bug fixes**
+
+- 2113, [FreeBSD]: __FreeBSD_version used in mem.c without including
+  <sys/param.h>
+
+5.9.1
+=====
 
-XXXX-XX-XX
+2022-05-20
 
 **Enhancements**
 
+- 1053_: drop Python 2.6 support.  (patches by Matthieu Darbois and Hugo van
+  Kemenade)
 - 2050_, [Linux]: increase ``read(2)`` buffer size from 1k to 32k when reading
   ``/proc`` pseudo files line by line. This should help having more consistent
   results.
 - 2057_, [OpenBSD]: add support for `cpu_freq()`_.
+- 2107_, [Linux]: `Process.memory_full_info()`_ (reporting process USS/PSS/Swap
+  memory) now reads ``/proc/pid/smaps_rollup`` instead of ``/proc/pids/smaps``,
+  which makes it 5 times faster.
 
 **Bug fixes**
 
 - 2048_: ``AttributeError`` is raised if ``psutil.Error`` class is raised
   manually and passed through ``str``.
-- 2049_, [Linux]: `cpu_freq`_ erroneously returns ``curr`` value in GHz while
+- 2049_, [Linux]: `cpu_freq()`_ erroneously returns ``curr`` value in GHz while
   ``min`` and ``max`` are in MHz.
 - 2050_, [Linux]: `virtual_memory()`_ may raise ``ValueError`` if running in a
   LCX container.
+- 2095_, [Linux]: `net_if_stats()` returns incorrect interface speed for 100GbE 
+  network cards
 
 5.9.0
 =====
diff --git a/MANIFEST.in b/MANIFEST.in
index 1816eb5f..c9cd85e9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -147,7 +147,6 @@ include scripts/internal/print_hashes.py
 include scripts/internal/print_timeline.py
 include scripts/internal/print_wheels.py
 include scripts/internal/purge_installation.py
-include scripts/internal/tidelift.py
 include scripts/internal/winmake.py
 include scripts/iotop.py
 include scripts/killall.py
diff --git a/Makefile b/Makefile
index a0a70739..8954e72f 100644
--- a/Makefile
+++ b/Makefile
@@ -9,15 +9,19 @@ TSCRIPT = psutil/tests/runner.py
 
 # Internal.
 DEPS = \
-	argparse \
-	autoflake \
+	git+https://github.com/PyCQA/autoflake.git@refs/pull/107/head \
 	autopep8 \
 	check-manifest \
 	concurrencytest \
 	coverage \
 	flake8 \
+	flake8-blind-except \
+	flake8-bugbear \
+	flake8-debugger \
 	flake8-print \
+	flake8-quotes \
 	isort \
+	pep8-naming \
 	pyperf \
 	pypinfo \
 	requests \
@@ -29,8 +33,7 @@ DEPS = \
 PY2_DEPS = \
 	futures \
 	ipaddress \
-	mock==1.0.1 \
-	unittest2
+	mock
 DEPS += `$(PYTHON) -c \
 	"import sys; print('$(PY2_DEPS)' if sys.version_info[0] == 2 else '')"`
 # "python3 setup.py build" can be parallelized on Python >= 3.6.
@@ -190,31 +193,35 @@ test-coverage:  ## Run test coverage.
 # Linters
 # ===================================================================
 
-check-flake8:  ## Run flake8 linter.
+flake8:  ## Run flake8 linter.
 	@git ls-files '*.py' | xargs $(PYTHON) -m flake8 --config=.flake8
 
-check-imports:  ## Run isort linter.
+isort:  ## Run isort linter.
 	@git ls-files '*.py' | xargs $(PYTHON) -m isort --settings=.isort.cfg --check-only
 
-check-c-code:  ## Run C linter.
+c-linter:  ## Run C linter.
 	@git ls-files '*.c' '*.h' | xargs $(PYTHON) scripts/internal/clinter.py
 
-lint:  ## Run all linters
-	${MAKE} check-flake8
-	${MAKE} check-imports
-	${MAKE} check-c-code
+lint-all:  ## Run all linters
+	${MAKE} flake8
+	${MAKE} isort
+	${MAKE} c-linter
 
 # ===================================================================
 # Fixers
 # ===================================================================
 
 fix-flake8:  ## Run autopep8, fix some Python flake8 / pep8 issues.
-	git ls-files | grep \\.py$ | xargs $(PYTHON) -m autopep8 --in-place --jobs 0 --global-config=.flake8
-	git ls-files | grep \\.py$ | xargs $(PYTHON) -m autoflake --in-place --remove-all-unused-imports --remove-unused-variables
+	@git ls-files '*.py' | xargs $(PYTHON) -m autopep8 --in-place --jobs 0 --global-config=.flake8
+	@git ls-files '*.py' | xargs $(PYTHON) -m autoflake --in-place --jobs 0 --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys
 
 fix-imports:  ## Fix imports with isort.
 	@git ls-files '*.py' | xargs $(PYTHON) -m isort --settings=.isort.cfg
 
+fix-all:  ## Run all code fixers.
+	${MAKE} fix-flake8
+	${MAKE} fix-imports
+
 # ===================================================================
 # GIT
 # ===================================================================
@@ -268,9 +275,6 @@ check-sdist:  ## Create source distribution and checks its sanity (MANIFEST)
 	build/venv/bin/python -m pip install -v --isolated --quiet dist/*.tar.gz
 	build/venv/bin/python -c "import os; os.chdir('build/venv'); import psutil"
 
-tidelift-relnotes:  ## upload release notes from HISTORY
-	$(PYTHON) scripts/internal/tidelift.py
-
 pre-release:  ## Check if we're ready to produce a new release.
 	${MAKE} check-sdist
 	${MAKE} install
@@ -294,7 +298,6 @@ release:  ## Create a release (down/uploads tar.gz, wheels, git tag release).
 	$(PYTHON) -c "import subprocess, sys; out = subprocess.check_output('git diff --quiet && git diff --cached --quiet', shell=True).strip(); sys.exit('there are uncommitted changes:\n%s' % out) if out else 0 ;"
 	$(PYTHON) -m twine upload dist/*  # upload tar.gz and Windows wheels on PyPI
 	${MAKE} git-tag-release
-	${MAKE} tidelift-relnotes
 
 check-manifest:  ## Inspect MANIFEST.in file.
 	$(PYTHON) -m check_manifest -v $(ARGS)
diff --git a/README.rst b/README.rst
index a1d737a7..0e0a29af 100644
--- a/README.rst
+++ b/README.rst
@@ -94,7 +94,7 @@ psutil currently supports the following platforms:
 - **Sun Solaris**
 - **AIX**
 
-Supported Python versions are **2.6**, **2.7**, **3.4+** and
+Supported Python versions are **2.7**, **3.4+** and
 `PyPy <http://pypy.org/>`__.
 
 Funding
diff --git a/appveyor.yml b/appveyor.yml
index 83e570ae..3c26939e 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -124,4 +124,5 @@ only_commits:
     - psutil/arch/windows/*
     - psutil/tests/*
     - scripts/*
+    - scripts/internal/*
     - setup.py
diff --git a/docs/index.rst b/docs/index.rst
index 82872a2b..8d663c7e 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -38,7 +38,7 @@ psutil currently supports the following platforms:
 - **Sun Solaris**
 - **AIX**
 
-Supported Python versions are **2.6**, **2.7** and **3.4+**.
+Supported Python versions are **2.7** and **3.4+**.
 `PyPy <http://pypy.org/>`__ is also known to work.
 
 The psutil documentation you're reading is distributed as a single HTML page.
@@ -2597,6 +2597,7 @@ If you want to develop psutil take a look at the `development guide`_.
 Platforms support history
 =========================
 
+* psutil 5.9.1 (2022-05): drop Python 2.6 support
 * psutil 5.9.0 (2021-12): **MidnightBSD**
 * psutil 5.8.0 (2020-12): **PyPy 2** on Windows
 * psutil 5.7.1 (2020-07): **Windows Nano**
@@ -2609,11 +2610,15 @@ Platforms support history
 * psutil 0.1.1 (2009-03): **FreeBSD**
 * psutil 0.1.0 (2009-01): **Linux, Windows, macOS**
 
-Supported Python versions are 2.6, 2.7, 3.4+ and PyPy3.
+Supported Python versions are 2.7, 3.4+ and PyPy3.
 
 Timeline
 ========
 
+- 2022-05-20:
+  `5.9.1 <https://pypi.org/project/psutil/5.9.1/#files>`__ -
+  `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#591>`__ -
+  `diff <https://github.com/giampaolo/psutil/compare/release-5.9.0...release-5.9.1#files_bucket>`__
 - 2021-12-29:
   `5.9.0 <https://pypi.org/project/psutil/5.9.0/#files>`__ -
   `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#590>`__ -
diff --git a/make.bat b/make.bat
index e292bb16..b424ed20 100644
--- a/make.bat
+++ b/make.bat
@@ -7,7 +7,7 @@ rem psutil ("make.bat build", "make.bat install") and running tests
 rem ("make.bat test").
 rem
 rem This script is modeled after my Windows installation which uses:
-rem - Visual studio 2008 for Python 2.6, 2.7
+rem - Visual studio 2008 for Python 2.7
 rem - Visual studio 2010 for Python 3.4+
 rem ...therefore it might not work on your Windows installation.
 rem
diff --git a/psutil/__init__.py b/psutil/__init__.py
index 6deebb26..1ba0c19f 100644
--- a/psutil/__init__.py
+++ b/psutil/__init__.py
@@ -17,7 +17,7 @@ sensors) in Python. Supported platforms:
  - Sun Solaris
  - AIX
 
-Works with Python versions from 2.6 to 3.4+.
+Works with Python versions 2.7 and 3.4+.
 """
 
 from __future__ import division
@@ -228,7 +228,7 @@ _SENTINEL = object()
 if (int(__version__.replace('.', '')) !=
         getattr(_psplatform.cext, 'version', None)):
     msg = "version conflict: %r C extension module was built for another " \
-          "version of psutil" % getattr(_psplatform.cext, "__file__")
+          "version of psutil" % _psplatform.cext.__file__
     if hasattr(_psplatform.cext, 'version'):
         msg += " (%s instead of %s)" % (
             '.'.join([x for x in str(_psplatform.cext.version)]), __version__)
@@ -380,10 +380,7 @@ class Process(object):
         self._ident = (self.pid, self._create_time)
 
     def __str__(self):
-        try:
-            info = collections.OrderedDict()
-        except AttributeError:  # pragma: no cover
-            info = {}  # Python 2.6
+        info = collections.OrderedDict()
         info["pid"] = self.pid
         if self._name:
             info['name'] = self._name
@@ -2075,7 +2072,7 @@ def disk_io_counters(perdisk=False, nowrap=True):
             rawdict[disk] = nt(*fields)
         return rawdict
     else:
-        return nt(*[sum(x) for x in zip(*rawdict.values())])
+        return nt(*(sum(x) for x in zip(*rawdict.values())))
 
 
 disk_io_counters.cache_clear = functools.partial(
diff --git a/psutil/_common.py b/psutil/_common.py
index ef3e8664..9937eb83 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -281,10 +281,7 @@ class Error(Exception):
     __module__ = 'psutil'
 
     def _infodict(self, attrs):
-        try:
-            info = collections.OrderedDict()
-        except AttributeError:  # pragma: no cover
-            info = {}  # Python 2.6
+        info = collections.OrderedDict()
         for name in attrs:
             value = getattr(self, name, None)
             if value:
diff --git a/psutil/_compat.py b/psutil/_compat.py
index 251e595f..52e762b1 100644
--- a/psutil/_compat.py
+++ b/psutil/_compat.py
@@ -232,8 +232,8 @@ except ImportError:
             return self.hashvalue
 
     def _make_key(args, kwds, typed,
-                  kwd_mark=(object(), ),
-                  fasttypes=set((int, str, frozenset, type(None))),
+                  kwd_mark=(_SENTINEL, ),
+                  fasttypes=set((int, str, frozenset, type(None))),  # noqa
                   sorted=sorted, tuple=tuple, type=type, len=len):
         key = args
         if kwds:
@@ -442,9 +442,9 @@ try:
 except ImportError:
     @contextlib.contextmanager
     def redirect_stderr(new_target):
-        original = getattr(sys, "stderr")
+        original = sys.stderr
         try:
-            setattr(sys, "stderr", new_target)
+            sys.stderr = new_target
             yield new_target
         finally:
-            setattr(sys, "stderr", original)
+            sys.stderr = original
diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py
index c4200cce..fd518985 100644
--- a/psutil/_psbsd.py
+++ b/psutil/_psbsd.py
@@ -453,7 +453,7 @@ if FREEBSD:
         return _common.sbattery(percent, secsleft, power_plugged)
 
     def sensors_temperatures():
-        "Return CPU cores temperatures if available, else an empty dict."
+        """Return CPU cores temperatures if available, else an empty dict."""
         ret = defaultdict(list)
         num_cpus = cpu_count_logical()
         for cpu in range(num_cpus):
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index afcb78f5..e7cef439 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -77,7 +77,8 @@ __extra__all__ = [
 
 
 POWER_SUPPLY_PATH = "/sys/class/power_supply"
-HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
+HAS_PROC_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
+HAS_PROC_SMAPS_ROLLUP = os.path.exists('/proc/%s/smaps_rollup' % os.getpid())
 HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get")
 HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get")
 
@@ -1875,18 +1876,42 @@ class Process(object):
                 [int(x) * PAGESIZE for x in f.readline().split()[:7]]
         return pmem(rss, vms, shared, text, lib, data, dirty)
 
-    # /proc/pid/smaps does not exist on kernels < 2.6.14 or if
-    # CONFIG_MMU kernel configuration option is not enabled.
-    if HAS_SMAPS:
+    if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS:
 
         @wrap_exceptions
-        def memory_full_info(
+        def _parse_smaps_rollup(self):
+            # /proc/pid/smaps_rollup was added to Linux in 2017. Faster
+            # than /proc/pid/smaps. It reports higher PSS than */smaps
+            # (from 1k up to 200k higher; tested against all processes).
+            uss = pss = swap = 0
+            try:
+                with open_binary("{}/{}/smaps_rollup".format(
+                        self._procfs_path, self.pid)) as f:
+                    for line in f:
+                        if line.startswith(b"Private_"):
+                            # Private_Clean, Private_Dirty, Private_Hugetlb
+                            uss += int(line.split()[1]) * 1024
+                        elif line.startswith(b"Pss:"):
+                            pss = int(line.split()[1]) * 1024
+                        elif line.startswith(b"Swap:"):
+                            swap = int(line.split()[1]) * 1024
+            except ProcessLookupError:  # happens on readline()
+                if not pid_exists(self.pid):
+                    raise NoSuchProcess(self.pid, self._name)
+                else:
+                    raise ZombieProcess(self.pid, self._name, self._ppid)
+            return (uss, pss, swap)
+
+        @wrap_exceptions
+        def _parse_smaps(
                 self,
                 # Gets Private_Clean, Private_Dirty, Private_Hugetlb.
                 _private_re=re.compile(br"\nPrivate.*:\s+(\d+)"),
                 _pss_re=re.compile(br"\nPss\:\s+(\d+)"),
                 _swap_re=re.compile(br"\nSwap\:\s+(\d+)")):
-            basic_mem = self.memory_info()
+            # /proc/pid/smaps does not exist on kernels < 2.6.14 or if
+            # CONFIG_MMU kernel configuration option is not enabled.
+
             # Note: using 3 regexes is faster than reading the file
             # line by line.
             # XXX: on Python 3 the 2 regexes are 30% slower than on
@@ -1905,12 +1930,20 @@ class Process(object):
             uss = sum(map(int, _private_re.findall(smaps_data))) * 1024
             pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024
             swap = sum(map(int, _swap_re.findall(smaps_data))) * 1024
+            return (uss, pss, swap)
+
+        def memory_full_info(self):
+            if HAS_PROC_SMAPS_ROLLUP:  # faster
+                uss, pss, swap = self._parse_smaps_rollup()
+            else:
+                uss, pss, swap = self._parse_smaps()
+            basic_mem = self.memory_info()
             return pfullmem(*basic_mem + (uss, pss, swap))
 
     else:
         memory_full_info = memory_info
 
-    if HAS_SMAPS:
+    if HAS_PROC_SMAPS:
 
         @wrap_exceptions
         def memory_maps(self):
diff --git a/psutil/_psutil_linux.c b/psutil/_psutil_linux.c
index 70cf5d1b..c59050cb 100644
--- a/psutil/_psutil_linux.c
+++ b/psutil/_psutil_linux.c
@@ -417,6 +417,7 @@ psutil_net_if_duplex_speed(PyObject* self, PyObject* args) {
     int sock = 0;
     int ret;
     int duplex;
+    __u32 uint_speed;
     int speed;
     struct ifreq ifr;
     struct ethtool_cmd ethcmd;
@@ -438,7 +439,15 @@ psutil_net_if_duplex_speed(PyObject* self, PyObject* args) {
 
     if (ret != -1) {
         duplex = ethcmd.duplex;
-        speed = ethcmd.speed;
+        // speed is returned from ethtool as a __u32 ranging from 0 to INT_MAX
+        // or SPEED_UNKNOWN (-1)
+        uint_speed = ethtool_cmd_speed(&ethcmd);
+        if (uint_speed == (__u32)SPEED_UNKNOWN || uint_speed > INT_MAX) {
+            speed = 0;
+        }
+        else {
+            speed = (int)uint_speed;
+        }
     }
     else {
         if ((errno == EOPNOTSUPP) || (errno == EINVAL)) {
diff --git a/psutil/arch/freebsd/mem.c b/psutil/arch/freebsd/mem.c
index f5374ef4..3326f63a 100644
--- a/psutil/arch/freebsd/mem.c
+++ b/psutil/arch/freebsd/mem.c
@@ -6,6 +6,7 @@
 
 
 #include <Python.h>
+#include <sys/param.h>
 #include <sys/sysctl.h>
 #include <sys/vmmeter.h>
 #include <vm/vm_param.h>
diff --git a/psutil/arch/freebsd/proc_socks.c b/psutil/arch/freebsd/proc_socks.c
index cdf5770b..737467a8 100644
--- a/psutil/arch/freebsd/proc_socks.c
+++ b/psutil/arch/freebsd/proc_socks.c
@@ -8,6 +8,7 @@
  */
 
 #include <Python.h>
+#include <sys/param.h>
 #include <sys/user.h>
 #include <sys/socketvar.h>    // for struct xsocket
 #include <sys/un.h>
diff --git a/psutil/arch/freebsd/sys_socks.c b/psutil/arch/freebsd/sys_socks.c
index 9f7cf8d5..56c89724 100644
--- a/psutil/arch/freebsd/sys_socks.c
+++ b/psutil/arch/freebsd/sys_socks.c
@@ -10,6 +10,7 @@
  */
 
 #include <Python.h>
+#include <sys/param.h>
 #include <sys/user.h>
 #include <sys/file.h>
 #include <sys/socketvar.h>    // for struct xsocket
diff --git a/psutil/arch/windows/wmi.c b/psutil/arch/windows/wmi.c
index 5fad4053..fc7a6652 100644
--- a/psutil/arch/windows/wmi.c
+++ b/psutil/arch/windows/wmi.c
@@ -71,7 +71,10 @@ psutil_init_loadavg_counter(PyObject *self, PyObject *args) {
 
     s = PdhAddEnglishCounterW(hQuery, szCounterPath, 0, &hCounter);
     if (s != ERROR_SUCCESS) {
-        PyErr_Format(PyExc_RuntimeError, "PdhAddEnglishCounterW failed");
+        PyErr_Format(
+            PyExc_RuntimeError,
+            "PdhAddEnglishCounterW failed. Performance counters may be disabled."
+        );
         return NULL;
     }
 
diff --git a/scripts/internal/convert_readme.py b/scripts/internal/convert_readme.py
index cca7dcb0..81f7bdb6 100755
--- a/scripts/internal/convert_readme.py
+++ b/scripts/internal/convert_readme.py
@@ -36,7 +36,7 @@ Sponsors
 
 `Add your logo <https://github.com/sponsors/giampaolo>`__.
 
-Example usages"""
+Example usages"""  # noqa
 
 
 def main():
diff --git a/scripts/internal/git_pre_commit.py b/scripts/internal/git_pre_commit.py
index c6f223bb..46b3bc53 100755
--- a/scripts/internal/git_pre_commit.py
+++ b/scripts/internal/git_pre_commit.py
@@ -109,14 +109,14 @@ def main():
                 print("%s:%s %r" % (path, lineno, line))
                 return exit("space at end of line")
             line = line.rstrip()
-            # pdb
-            if "pdb.set_trace" in line:
-                print("%s:%s %s" % (path, lineno, line))
-                return exit("you forgot a pdb in your python code")
-            # bare except clause
-            if "except:" in line and not line.endswith("# NOQA"):
-                print("%s:%s %s" % (path, lineno, line))
-                return exit("bare except clause")
+            # # pdb (now provided by flake8-debugger plugin)
+            # if "pdb.set_trace" in line:
+            #     print("%s:%s %s" % (path, lineno, line))
+            #     return exit("you forgot a pdb in your python code")
+            # # bare except clause (now provided by flake8-blind-except plugin)
+            # if "except:" in line and not line.endswith("# NOQA"):
+            #     print("%s:%s %s" % (path, lineno, line))
+            #     return exit("bare except clause")
 
     # Python linters
     if py_files:
diff --git a/scripts/internal/print_announce.py b/scripts/internal/print_announce.py
index c9948c1d..c2113f36 100755
--- a/scripts/internal/print_announce.py
+++ b/scripts/internal/print_announce.py
@@ -47,7 +47,7 @@ line tools such as: ps, top, lsof, netstat, ifconfig, who, df, kill, free, \
 nice, ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap. It \
 currently supports Linux, Windows, macOS, Sun Solaris, FreeBSD, OpenBSD, \
 NetBSD and AIX, both 32-bit and 64-bit architectures.  Supported Python \
-versions are 2.6, 2.7 and 3.4+. PyPy is also known to work.
+versions are 2.7 and 3.4+. PyPy is also known to work.
 
 What's new
 ==========
@@ -83,7 +83,7 @@ def get_changes():
     block = []
 
     # eliminate the part preceding the first block
-    for i, line in enumerate(lines):
+    for line in lines:
         line = lines.pop(0)
         if line.startswith('===='):
             break
@@ -92,10 +92,8 @@ def get_changes():
     for i, line in enumerate(lines):
         line = lines.pop(0)
         line = line.rstrip()
-        if re.match(r"^- \d+_: ", line):
-            num, _, rest = line.partition(': ')
-            num = ''.join([x for x in num if x.isdigit()])
-            line = "- #%s: %s" % (num, rest)
+        if re.match(r"^- \d+_", line):
+            line = re.sub(r"^- (\d+)_", r"- #\1", line)
 
         if line.startswith('===='):
             break
diff --git a/scripts/internal/tidelift.py b/scripts/internal/tidelift.py
deleted file mode 100755
index 9470fc85..00000000
--- a/scripts/internal/tidelift.py
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""
-Update news entry of Tidelift with latest HISTORY.rst section.
-Put your Tidelift API token in a file first:
-~/.tidelift.token
-"""
-
-from __future__ import print_function
-
-import os
-
-import requests
-
-import psutil
-from psutil.tests import import_module_by_path
-
-
-def upload_relnotes(package, version, text, token):
-    url = "https://api.tidelift.com/external-api/" + \
-          "lifting/pypi/%s/release-notes/%s" % (package, version)
-    res = requests.put(
-        url=url,
-        data=text.encode('utf8'),
-        headers={"Authorization": "Bearer: %s" % token})
-    print(version, res.status_code, res.text)
-    res.raise_for_status()
-
-
-def main():
-    here = os.path.abspath(os.path.dirname(__file__))
-    path = os.path.join(here, "print_announce.py")
-    get_changes = import_module_by_path(path).get_changes
-    with open(os.path.expanduser("~/.tidelift.token")) as f:
-        token = f.read().strip()
-    upload_relnotes('psutil', psutil.__version__, get_changes(), token)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/scripts/internal/winmake.py b/scripts/internal/winmake.py
index 6c482975..bb880051 100755
--- a/scripts/internal/winmake.py
+++ b/scripts/internal/winmake.py
@@ -41,17 +41,21 @@ PYPY = '__pypy__' in sys.builtin_module_names
 DEPS = [
     "coverage",
     "flake8",
+    "flake8-blind-except",
+    "flake8-debugger",
+    "flake8-print",
     "nose",
     "pdbpp",
     "pip",
     "pyperf",
     "pyreadline",
+    "requests",
     "setuptools",
     "wheel",
-    "requests"
 ]
-if sys.version_info[:2] <= (2, 7):
-    DEPS.append('unittest2')
+
+if sys.version_info[:2] >= (3, 5):
+    DEPS.append('flake8-bugbear')
 if sys.version_info[:2] <= (2, 7):
     DEPS.append('mock')
 if sys.version_info[:2] <= (3, 2):
diff --git a/setup.py b/setup.py
index 3bad6d5d..a6f6eb16 100755
--- a/setup.py
+++ b/setup.py
@@ -77,7 +77,6 @@ extras_require = {"test": [
     "enum34; python_version <= '3.4'",
     "ipaddress; python_version < '3.0'",
     "mock; python_version < '3.0'",
-    "unittest2; python_version < '3.0'",
 ]}
 if not PYPY:
     extras_require['test'].extend([
@@ -392,7 +391,6 @@ def main():
             'Operating System :: POSIX',
             'Programming Language :: C',
             'Programming Language :: Python :: 2',
-            'Programming Language :: Python :: 2.6',
             'Programming Language :: Python :: 2.7',
             'Programming Language :: Python :: 3',
             'Programming Language :: Python :: Implementation :: CPython',
@@ -414,7 +412,7 @@ def main():
     )
     if setuptools is not None:
         kwargs.update(
-            python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
+            python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
             extras_require=extras_require,
             zip_safe=False,
         )


Test patch:
diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py
index 0d0e00df..878af411 100644
--- a/psutil/tests/__init__.py
+++ b/psutil/tests/__init__.py
@@ -31,6 +31,7 @@ import tempfile
 import textwrap
 import threading
 import time
+import unittest
 import warnings
 from socket import AF_INET
 from socket import AF_INET6
@@ -57,11 +58,6 @@ from psutil._compat import unicode
 from psutil._compat import which
 
 
-if PY3:
-    import unittest
-else:
-    import unittest2 as unittest  # requires "pip install unittest2"
-
 try:
     from unittest import mock  # py3
 except ImportError:
@@ -743,7 +739,7 @@ def call_until(fun, expr):
 
 
 def safe_rmpath(path):
-    "Convenience function for removing temporary test files or dirs"
+    """Convenience function for removing temporary test files or dirs."""
     def retry_fun(fun):
         # On Windows it could happen that the file or directory has
         # open handles or references preventing the delete operation
@@ -776,7 +772,7 @@ def safe_rmpath(path):
 
 
 def safe_mkdir(dir):
-    "Convenience function for creating a directory"
+    """Convenience function for creating a directory."""
     try:
         os.mkdir(dir)
     except FileExistsError:
@@ -785,7 +781,7 @@ def safe_mkdir(dir):
 
 @contextlib.contextmanager
 def chdir(dirname):
-    "Context manager which temporarily changes the current directory."
+    """Context manager which temporarily changes the current directory."""
     curdir = os.getcwd()
     try:
         os.chdir(dirname)
@@ -862,6 +858,11 @@ class TestCase(unittest.TestCase):
         def runTest(self):
             pass
 
+        @contextlib.contextmanager
+        def subTest(self, *args, **kw):
+            # fake it for python 2.7
+            yield
+
 
 # monkey patch default unittest.TestCase
 unittest.TestCase = TestCase
diff --git a/psutil/tests/test_aix.py b/psutil/tests/test_aix.py
index 6a5debfa..4a23b774 100755
--- a/psutil/tests/test_aix.py
+++ b/psutil/tests/test_aix.py
@@ -9,12 +9,12 @@
 """AIX specific tests."""
 
 import re
+import unittest
 
 import psutil
 from psutil import AIX
 from psutil.tests import PsutilTestCase
 from psutil.tests import sh
-from psutil.tests import unittest
 
 
 @unittest.skipIf(not AIX, "AIX only")
diff --git a/psutil/tests/test_bsd.py b/psutil/tests/test_bsd.py
index 1ae810f1..a236810d 100755
--- a/psutil/tests/test_bsd.py
+++ b/psutil/tests/test_bsd.py
@@ -14,6 +14,7 @@ import datetime
 import os
 import re
 import time
+import unittest
 
 import psutil
 from psutil import BSD
@@ -27,7 +28,6 @@ from psutil.tests import retry_on_failure
 from psutil.tests import sh
 from psutil.tests import spawn_testproc
 from psutil.tests import terminate
-from psutil.tests import unittest
 from psutil.tests import which
 
 
diff --git a/psutil/tests/test_connections.py b/psutil/tests/test_connections.py
index 5381608a..f3b1f837 100755
--- a/psutil/tests/test_connections.py
+++ b/psutil/tests/test_connections.py
@@ -9,6 +9,7 @@
 import os
 import socket
 import textwrap
+import unittest
 from contextlib import closing
 from socket import AF_INET
 from socket import AF_INET6
@@ -39,7 +40,6 @@ from psutil.tests import retry_on_failure
 from psutil.tests import serialrun
 from psutil.tests import skip_on_access_denied
 from psutil.tests import tcp_socketpair
-from psutil.tests import unittest
 from psutil.tests import unix_socketpair
 from psutil.tests import wait_for_file
 
diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py
index 9def408a..5c927d68 100755
--- a/psutil/tests/test_contracts.py
+++ b/psutil/tests/test_contracts.py
@@ -17,6 +17,7 @@ import stat
 import sys
 import time
 import traceback
+import unittest
 
 import psutil
 from psutil import AIX
@@ -51,7 +52,6 @@ from psutil.tests import is_namedtuple
 from psutil.tests import kernel_version
 from psutil.tests import process_namespace
 from psutil.tests import serialrun
-from psutil.tests import unittest
 
 
 # ===================================================================
diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py
index 0d42d051..51e8be51 100755
--- a/psutil/tests/test_linux.py
+++ b/psutil/tests/test_linux.py
@@ -20,6 +20,7 @@ import socket
 import struct
 import textwrap
 import time
+import unittest
 import warnings
 
 import psutil
@@ -46,7 +47,6 @@ from psutil.tests import retry_on_failure
 from psutil.tests import safe_rmpath
 from psutil.tests import sh
 from psutil.tests import skip_on_not_implemented
-from psutil.tests import unittest
 from psutil.tests import which
 
 
@@ -1775,28 +1775,19 @@ class TestSensorsFans(PsutilTestCase):
 class TestProcess(PsutilTestCase):
 
     @retry_on_failure()
-    def test_memory_full_info(self):
-        testfn = self.get_testfn()
-        src = textwrap.dedent("""
-            import time
-            with open("%s", "w") as f:
-                time.sleep(10)
-            """ % testfn)
-        sproc = self.pyrun(src)
-        call_until(lambda: os.listdir('.'), "'%s' not in ret" % testfn)
-        p = psutil.Process(sproc.pid)
-        time.sleep(.1)
-        mem = p.memory_full_info()
-        maps = p.memory_maps(grouped=False)
+    def test_parse_smaps_vs_memory_maps(self):
+        sproc = self.spawn_testproc()
+        uss, pss, swap = psutil._pslinux.Process(sproc.pid)._parse_smaps()
+        maps = psutil.Process(sproc.pid).memory_maps(grouped=False)
         self.assertAlmostEqual(
-            mem.uss, sum([x.private_dirty + x.private_clean for x in maps]),
+            uss, sum([x.private_dirty + x.private_clean for x in maps]),
             delta=4096)
         self.assertAlmostEqual(
-            mem.pss, sum([x.pss for x in maps]), delta=4096)
+            pss, sum([x.pss for x in maps]), delta=4096)
         self.assertAlmostEqual(
-            mem.swap, sum([x.swap for x in maps]), delta=4096)
+            swap, sum([x.swap for x in maps]), delta=4096)
 
-    def test_memory_full_info_mocked(self):
+    def test_parse_smaps_mocked(self):
         # See: https://github.com/giampaolo/psutil/issues/1222
         with mock_open_content(
             "/proc/%s/smaps" % os.getpid(),
@@ -1823,12 +1814,12 @@ class TestProcess(PsutilTestCase):
                 Locked:                19 kB
                 VmFlags: rd ex
                 """).encode()) as m:
-            p = psutil.Process()
-            mem = p.memory_full_info()
+            p = psutil._pslinux.Process(os.getpid())
+            uss, pss, swap = p._parse_smaps()
             assert m.called
-            self.assertEqual(mem.uss, (6 + 7 + 14) * 1024)
-            self.assertEqual(mem.pss, 3 * 1024)
-            self.assertEqual(mem.swap, 15 * 1024)
+            self.assertEqual(uss, (6 + 7 + 14) * 1024)
+            self.assertEqual(pss, 3 * 1024)
+            self.assertEqual(swap, 15 * 1024)
 
     # On PYPY file descriptors are not closed fast enough.
     @unittest.skipIf(PYPY, "unreliable on PYPY")
diff --git a/psutil/tests/test_memleaks.py b/psutil/tests/test_memleaks.py
index d5baffa5..e507e837 100755
--- a/psutil/tests/test_memleaks.py
+++ b/psutil/tests/test_memleaks.py
@@ -19,6 +19,7 @@ from __future__ import print_function
 
 import functools
 import os
+import unittest
 
 import psutil
 import psutil._common
@@ -50,7 +51,6 @@ from psutil.tests import skip_on_access_denied
 from psutil.tests import spawn_testproc
 from psutil.tests import system_namespace
 from psutil.tests import terminate
-from psutil.tests import unittest
 
 
 cext = psutil._psplatform.cext
diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py
index aa30cbd6..e22789c1 100755
--- a/psutil/tests/test_misc.py
+++ b/psutil/tests/test_misc.py
@@ -17,6 +17,7 @@ import os
 import pickle
 import socket
 import stat
+import unittest
 
 import psutil
 import psutil.tests
@@ -51,7 +52,6 @@ from psutil.tests import import_module_by_path
 from psutil.tests import mock
 from psutil.tests import reload_module
 from psutil.tests import sh
-from psutil.tests import unittest
 
 
 # ===================================================================
@@ -296,7 +296,7 @@ class TestCommonModule(PsutilTestCase):
     def test_memoize(self):
         @memoize
         def foo(*args, **kwargs):
-            "foo docstring"
+            """foo docstring"""
             calls.append(None)
             return (args, kwargs)
 
@@ -402,7 +402,7 @@ class TestCommonModule(PsutilTestCase):
                 supports_ipv6.cache_clear()
                 assert s.called
         else:
-            with self.assertRaises(Exception):
+            with self.assertRaises(socket.error):
                 sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
                 try:
                     sock.bind(("::1", 0))
diff --git a/psutil/tests/test_osx.py b/psutil/tests/test_osx.py
index 4f4b1c29..d0f588ad 100755
--- a/psutil/tests/test_osx.py
+++ b/psutil/tests/test_osx.py
@@ -8,6 +8,7 @@
 
 import re
 import time
+import unittest
 
 import psutil
 from psutil import MACOS
@@ -20,7 +21,6 @@ from psutil.tests import retry_on_failure
 from psutil.tests import sh
 from psutil.tests import spawn_testproc
 from psutil.tests import terminate
-from psutil.tests import unittest
 
 
 if POSIX:
diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py
index edef7e7d..ebbf7a6e 100755
--- a/psutil/tests/test_posix.py
+++ b/psutil/tests/test_posix.py
@@ -13,6 +13,7 @@ import os
 import re
 import subprocess
 import time
+import unittest
 
 import psutil
 from psutil import AIX
@@ -32,7 +33,6 @@ from psutil.tests import sh
 from psutil.tests import skip_on_access_denied
 from psutil.tests import spawn_testproc
 from psutil.tests import terminate
-from psutil.tests import unittest
 from psutil.tests import which
 
 
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index 181097e3..eb17e239 100755
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -19,6 +19,7 @@ import sys
 import textwrap
 import time
 import types
+import unittest
 
 import psutil
 from psutil import AIX
@@ -62,7 +63,6 @@ from psutil.tests import retry_on_failure
 from psutil.tests import sh
 from psutil.tests import skip_on_access_denied
 from psutil.tests import skip_on_not_implemented
-from psutil.tests import unittest
 from psutil.tests import wait_for_pid
 
 
@@ -1574,7 +1574,7 @@ class TestPopen(PsutilTestCase):
             self.assertRaises(psutil.NoSuchProcess, proc.kill)
             self.assertRaises(psutil.NoSuchProcess, proc.send_signal,
                               signal.SIGTERM)
-            if WINDOWS and sys.version_info >= (2, 7):
+            if WINDOWS:
                 self.assertRaises(psutil.NoSuchProcess, proc.send_signal,
                                   signal.CTRL_C_EVENT)
                 self.assertRaises(psutil.NoSuchProcess, proc.send_signal,
diff --git a/psutil/tests/test_sunos.py b/psutil/tests/test_sunos.py
index ad94f774..dd74a49b 100755
--- a/psutil/tests/test_sunos.py
+++ b/psutil/tests/test_sunos.py
@@ -7,12 +7,12 @@
 """Sun OS specific tests."""
 
 import os
+import unittest
 
 import psutil
 from psutil import SUNOS
 from psutil.tests import PsutilTestCase
 from psutil.tests import sh
-from psutil.tests import unittest
 
 
 @unittest.skipIf(not SUNOS, "SUNOS only")
diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py
index bb5edd5c..e130c935 100755
--- a/psutil/tests/test_system.py
+++ b/psutil/tests/test_system.py
@@ -16,6 +16,7 @@ import signal
 import socket
 import sys
 import time
+import unittest
 
 import psutil
 from psutil import AIX
@@ -50,7 +51,6 @@ from psutil.tests import check_net_address
 from psutil.tests import enum
 from psutil.tests import mock
 from psutil.tests import retry_on_failure
-from psutil.tests import unittest
 
 
 # ===================================================================
diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py
index 89888dfe..dd98538c 100755
--- a/psutil/tests/test_testutils.py
+++ b/psutil/tests/test_testutils.py
@@ -16,6 +16,7 @@ import os
 import socket
 import stat
 import subprocess
+import unittest
 
 import psutil
 import psutil.tests
@@ -48,7 +49,6 @@ from psutil.tests import serialrun
 from psutil.tests import system_namespace
 from psutil.tests import tcp_socketpair
 from psutil.tests import terminate
-from psutil.tests import unittest
 from psutil.tests import unix_socketpair
 from psutil.tests import wait_for_file
 from psutil.tests import wait_for_pid
diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py
index e6357269..3fa3f017 100755
--- a/psutil/tests/test_unicode.py
+++ b/psutil/tests/test_unicode.py
@@ -76,6 +76,7 @@ etc.) and make sure that:
 import os
 import shutil
 import traceback
+import unittest
 import warnings
 from contextlib import closing
 
@@ -108,7 +109,6 @@ from psutil.tests import serialrun
 from psutil.tests import skip_on_access_denied
 from psutil.tests import spawn_testproc
 from psutil.tests import terminate
-from psutil.tests import unittest
 
 
 if APPVEYOR:
diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py
index 7a843bc3..b89c67a9 100755
--- a/psutil/tests/test_windows.py
+++ b/psutil/tests/test_windows.py
@@ -17,6 +17,7 @@ import signal
 import subprocess
 import sys
 import time
+import unittest
 import warnings
 
 import psutil
@@ -36,7 +37,6 @@ from psutil.tests import retry_on_failure
 from psutil.tests import sh
 from psutil.tests import spawn_testproc
 from psutil.tests import terminate
-from psutil.tests import unittest
 
 
 if WINDOWS and not PYPY:
@@ -338,8 +338,6 @@ class TestProcess(WindowsTestCase):
         win32api.CloseHandle(handle)
         self.assertEqual(p.num_handles(), before)
 
-    @unittest.skipIf(not sys.version_info >= (2, 7),
-                     "CTRL_* signals not supported")
     def test_ctrl_signals(self):
         p = psutil.Process(self.spawn_testproc().pid)
         p.send_signal(signal.CTRL_C_EVENT)