summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2015-07-09 11:43:48 +0200
committerGiampaolo Rodola <g.rodola@gmail.com>2015-07-09 11:43:48 +0200
commit192aa78c90253bede27b2745d4e80aeed79a3aef (patch)
tree3288344a8695ac4fd53162546fb051229c921e4d
parenta6cb7f927f40bd129f31493c37dfe7d0d6f6a070 (diff)
parent73f54f47a7d4eac329c27af71c7fc5ad432d2e84 (diff)
downloadpsutil-get_open_files_thread.tar.gz
Merge branch 'master' into get_open_files_threadget_open_files_thread
-rwxr-xr-x[-rw-r--r--].git-pre-commit72
-rw-r--r--.travis.yml13
-rw-r--r--CREDITS22
-rw-r--r--HISTORY.rst34
-rw-r--r--INSTALL.rst2
-rw-r--r--MANIFEST.in3
-rw-r--r--Makefile50
-rw-r--r--README.rst4
-rw-r--r--docs/index.rst22
-rw-r--r--make.bat160
-rw-r--r--psutil/__init__.py57
-rw-r--r--psutil/_common.py12
-rw-r--r--psutil/_psbsd.py6
-rw-r--r--psutil/_pslinux.py128
-rw-r--r--psutil/_pssunos.py4
-rw-r--r--psutil/_psutil_sunos.c8
-rw-r--r--psutil/_psutil_windows.c17
-rw-r--r--psutil/arch/windows/process_info.c5
-rw-r--r--test/README15
-rw-r--r--test/README.rst21
-rw-r--r--test/_bsd.py11
-rw-r--r--test/_linux.py162
-rw-r--r--test/_osx.py9
-rw-r--r--test/_posix.py7
-rw-r--r--test/_sunos.py10
-rw-r--r--test/_windows.py18
-rw-r--r--test/test_memory_leaks.py39
-rw-r--r--test/test_psutil.py131
-rw-r--r--tox.ini3
29 files changed, 745 insertions, 300 deletions
diff --git a/.git-pre-commit b/.git-pre-commit
index da628495..3a6161f4 100644..100755
--- a/.git-pre-commit
+++ b/.git-pre-commit
@@ -1,25 +1,47 @@
-#!/bin/bash
-
-# This script gets executed on "git commit -am '...'".
-# You can install it with "make install-git-hooks".
-
-# pdb
-pdb=`git diff --cached --name-only | \
- grep -E '\.py$' | \
- xargs grep -n -H -E '(pdb\.set_trace\(\))'`
-if [ ! -z "$pdb" ] ; then
- echo "commit aborted: you forgot a pdb in your python code"
- echo $pdb
- exit 1
-fi
-
-# flake8
-flake8=`git diff --cached --name-only | grep -E '\.py$'`
-if [ ! -z "$flake8" ] ; then
- flake8=`echo "$flake8" | xargs flake8`
- if [ ! -z "$flake8" ] ; then
- echo "commit aborted: python code is not flake8-compliant"
- echo $flake8
- exit 1
- fi
-fi
+#!/usr/bin/env python
+
+# This gets executed on 'git commit' and rejects the commit in case the
+# submitted code does not pass validation.
+# Install it with "make install-git-hooks"
+
+import os
+import subprocess
+import sys
+
+
+def main():
+ out = subprocess.check_output("git diff --cached --name-only", shell=True)
+ files = [x for x in out.split('\n') if x.endswith('.py') and
+ os.path.exists(x)]
+
+ for path in files:
+ with open(path) as f:
+ data = f.read()
+
+ # pdb
+ if "pdb.set_trace" in data:
+ for lineno, line in enumerate(data.split('\n'), 1):
+ line = line.rstrip()
+ if "pdb.set_trace" in line:
+ print("%s: %s" % (lineno, line))
+ sys.exit(
+ "commit aborted: you forgot a pdb in your python code")
+
+ # bare except clause
+ if "except:" in data:
+ for lineno, line in enumerate(data.split('\n'), 1):
+ line = line.rstrip()
+ if "except:" in line and not line.endswith("# NOQA"):
+ print("%s: %s" % (lineno, line))
+ sys.exit("commit aborted: bare except clause")
+
+ # flake8
+ failed = False
+ for path in files:
+ ret = subprocess.call("flake8 %s" % path, shell=True)
+ if ret != 0:
+ failed = True
+ if failed:
+ sys.exit("commit aborted: python code is not flake8-compliant")
+
+main()
diff --git a/.travis.yml b/.travis.yml
index 202d4877..c4085bc8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,17 +7,18 @@ python:
- 3.4
# - pypy
install:
- - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install ipaddress unittest2; fi
- - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install ipaddress; fi
- - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then pip install ipaddress; fi
- - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install ipaddress; fi
+ - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install -U ipaddress unittest2 mock; fi
+ - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install -U ipaddress mock; fi
+ - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then pip install -U ipaddress mock; fi
+ - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install -U ipaddress; fi
script:
- - pip install flake8
+ - pip install flake8 pep8
- python setup.py build
- python setup.py install
- python test/test_psutil.py
- python test/test_memory_leaks.py
- - make flake8
+ - flake8
+ - pep8
os:
- linux
- osx
diff --git a/CREDITS b/CREDITS
index a1ec04e2..37c653f2 100644
--- a/CREDITS
+++ b/CREDITS
@@ -255,7 +255,7 @@ I: 492
N: Jeff Tang
W: https://github.com/mrjefftang
-I: 340, 529
+I: 340, 529, 616
N: Yaolong Huang
E: airekans@gmail.com
@@ -289,4 +289,22 @@ I: 578, 581, 587
N: spacewanderlzx
C: Guangzhou,China
E: spacewanderlzx@gmail.com
-I: 555 \ No newline at end of file
+I: 555
+
+N: Fabian Groffen
+I: 611, 618
+
+N: desbma
+W: https://github.com/desbma
+C: France
+I: 628
+
+N: John Burnett
+W: http://www.johnburnett.com/
+C: Irvine, CA, US
+I: 614
+
+N: Árni Már Jónsson
+E: Reykjavik, Iceland
+E: https://github.com/arnimarj
+I: 634
diff --git a/HISTORY.rst b/HISTORY.rst
index f8098ea8..a8e8772a 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -1,6 +1,30 @@
Bug tracker at https://github.com/giampaolo/psutil/issues
-3.0.0 - XXXX-XX-XX
+3.0.2 - XXXX-XX-XX
+==================
+
+**Bug fixes**
+
+- #636: [Windows] Process.memory_info() raise AccessDenied.
+- #637: [UNIX] raise exception if trying to send signal to Process PID 0 as it
+ will affect os.getpid()'s process group instead of PID 0.
+- #639: [Linux] Process.cmdline() can be truncated.
+- #640: [Linux] *connections functions may swallow errors and return an
+ incomplete list of connnections.
+
+
+3.0.1 - 2015-06-18
+==================
+
+**Bug fixes**
+
+- #632: [Linux] better error message if cannot parse process UNIX connections.
+- #634: [Linux] Proces.cmdline() does not include empty string arguments.
+- #635: [UNIX] crash on module import if 'enum' package is installed on python
+ < 3.4.
+
+
+3.0.0 - 2015-06-13
==================
**Enhancements**
@@ -21,6 +45,8 @@ Bug tracker at https://github.com/giampaolo/psutil/issues
- #599: [Windows] process name() can now be determined for all processes even
when running as a limited user.
- #602: pre-commit GIT hook.
+- #629: enhanced support for py.test and nose test discovery and tests run.
+- #616: [Windows] Add inet_ntop function for Windows XP.
**Bug fixes**
@@ -36,6 +62,12 @@ Bug tracker at https://github.com/giampaolo/psutil/issues
number is provided.
- #593: [FreeBSD] Process().memory_maps() segfaults.
- #606: Process.parent() may swallow NoSuchProcess exceptions.
+- #611: [SunOS] net_io_counters has send and received swapped
+- #614: [Linux]: cpu_count(logical=False) return the number of physical CPUs
+ instead of physical cores.
+- #618: [SunOS] swap tests fail on Solaris when run as normal user
+- #628: [Linux] Process.name() truncates process name in case it contains
+ spaces or parentheses.
2.2.1 - 2015-02-02
diff --git a/INSTALL.rst b/INSTALL.rst
index f402b924..e518c430 100644
--- a/INSTALL.rst
+++ b/INSTALL.rst
@@ -56,7 +56,7 @@ Installing on Linux
===================
gcc is required and so the python headers. They can easily be installed by
-using the distro package manager. For example, on Debian amd Ubuntu::
+using the distro package manager. For example, on Debian and Ubuntu::
$ sudo apt-get install gcc python-dev
diff --git a/MANIFEST.in b/MANIFEST.in
index 91f24647..18136122 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,7 @@
include .git-pre-commit
include .gitignore
include .travis.yml
+include .git-pre-commit
include CREDITS
include HISTORY.rst
include INSTALL.rst
@@ -16,4 +17,4 @@ recursive-include docs *
recursive-exclude docs/_build *
recursive-include examples *.py
recursive-include psutil *.py *.c *.h
-recursive-include test *.py README \ No newline at end of file
+recursive-include test *.py README*
diff --git a/Makefile b/Makefile
index dead5612..2c9817a9 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,9 @@
PYTHON = python
TSCRIPT = test/test_psutil.py
+# Private vars
+COVERAGE_OPTS = --include="*psutil*" --omit="test/*,*setup*"
+
all: test
clean:
@@ -19,10 +22,12 @@ clean:
rm -rf *.core
rm -rf *.egg-info
rm -rf *\$testfile*
+ rm -rf .coverage
rm -rf .tox
rm -rf build
rm -rf dist
rm -rf docs/_build
+ rm -rf htmlcov
build: clean
$(PYTHON) setup.py build
@@ -31,6 +36,29 @@ build: clean
@# this directory.
$(PYTHON) setup.py build_ext -i
+# useful deps which are nice to have while developing / testing
+install-dev-deps:
+ python -c "import urllib2; \
+ r = urllib2.urlopen('https://bootstrap.pypa.io/get-pip.py'); \
+ open('/tmp/get-pip.py', 'w').write(r.read());"
+ $(PYTHON) /tmp/get-pip.py --user
+ rm /tmp/get-pip.py
+ $(PYTHON) -m pip install --user --upgrade pip
+ $(PYTHON) -m pip install --user --upgrade \
+ # mandatory for unittests
+ ipaddress \
+ mock \
+ unittest2 \
+ # nice to have
+ coverage \
+ flake8 \
+ ipdb \
+ nose \
+ pep8 \
+ pyflakes \
+ sphinx \
+ sphinx-pypi-upload \
+
install: build
$(PYTHON) setup.py install --user; \
@@ -55,23 +83,27 @@ test-memleaks: install
test-by-name: install
@$(PYTHON) -m nose test/test_psutil.py --nocapture -v -m $(filter-out $@,$(MAKECMDGOALS))
-# same as above but for test_memory_leaks.py script
+# Same as above but for test_memory_leaks.py script.
test-memleaks-by-name: install
@$(PYTHON) -m nose test/test_memory_leaks.py --nocapture -v -m $(filter-out $@,$(MAKECMDGOALS))
-# requires "pip install pep8"
+coverage: install
+ rm -rf .coverage htmlcov
+ $(PYTHON) -m coverage run $(TSCRIPT) $(COVERAGE_OPTS)
+ $(PYTHON) -m coverage report $(COVERAGE_OPTS)
+ @echo "writing results to htmlcov/index.html"
+ $(PYTHON) -m coverage html $(COVERAGE_OPTS)
+ $(PYTHON) -m webbrowser -t htmlcov/index.html
+
pep8:
- @git ls-files | grep \\.py$ | xargs pep8
+ @git ls-files | grep \\.py$ | xargs $(PYTHON) -m pep8
-# requires "pip install pyflakes"
pyflakes:
@export PYFLAKES_NODOCTEST=1 && \
- git ls-files | grep \\.py$ | xargs pyflakes
+ git ls-files | grep \\.py$ | xargs $(PYTHON) -m pyflakes
-# requires "pip install flake8"
flake8:
- @git ls-files | grep \\.py$ | xargs flake8
-
+ @git ls-files | grep \\.py$ | xargs $(PYTHON) -m flake8
# Upload source tarball on https://pypi.python.org/pypi/psutil.
upload-src: clean
@@ -90,5 +122,5 @@ git-tag-release:
# install GIT pre-commit hook
install-git-hooks:
- cp .git-pre-commit .git/hooks/pre-commit
+ ln -sf ../../.git-pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
diff --git a/README.rst b/README.rst
index c34a63d6..f2f68c7c 100644
--- a/README.rst
+++ b/README.rst
@@ -38,7 +38,7 @@ running processes**. It implements many functionalities offered by command 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, OSX, FreeBSD** and **Sun Solaris**, both **32-bit** and
-**64-bit** architectures, with Python versions from **2.6 to 3.4** (users of
+**64-bit** architectures, with Python versions from **2.6 to 3.5** (users of
Python 2.4 and 2.5 may use `2.1.3 <https://pypi.python.org/pypi?name=psutil&version=2.1.3&:action=files>`__ version).
`PyPy <http://pypy.org/>`__ is also known to work.
@@ -333,6 +333,8 @@ http://groups.google.com/group/psutil/
Timeline
========
+- 2015-06-18: `psutil-3.0.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-3.0.1.tar.gz>`_
+- 2015-06-13: `psutil-3.0.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-3.0.0.tar.gz>`_
- 2015-02-02: `psutil-2.2.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.2.1.tar.gz>`_
- 2015-01-06: `psutil-2.2.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.2.0.tar.gz>`_
- 2014-09-26: `psutil-2.1.3.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.1.3.tar.gz>`_
diff --git a/docs/index.rst b/docs/index.rst
index efbab674..51cc6c7b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -568,7 +568,7 @@ Functions
import psutil
def on_terminate(proc):
- print("process {} terminated".format(proc))
+ print("process {} terminated with exit code {}".format(proc, proc.returncode))
procs = [...] # a list of Process instances
for p in procs:
@@ -598,9 +598,8 @@ Exceptions
method called the OS may be able to succeed in retrieving the process
information or not.
Note: this is a subclass of :class:`NoSuchProcess` so if you're not
- interested in retrieving zombies while iterating over all processes (e.g.
- via :func:`process_iter()`) you can ignore this exception and just catch
- :class:`NoSuchProcess`.
+ interested in retrieving zombies (e.g. when using :func:`process_iter()`)
+ you can ignore this exception and just catch :class:`NoSuchProcess`.
*New in 3.0.0*
@@ -769,6 +768,13 @@ Process class
10
>>>
+ Starting from `Python 3.3 <http://bugs.python.org/issue10784>`__ this
+ functionality is also available as
+ `os.getpriority() <http://docs.python.org/3/library/os.html#os.getpriority>`__
+ and
+ `os.setpriority() <http://docs.python.org/3/library/os.html#os.setpriority>`__
+ (UNIX only).
+
On Windows this is available as well by using
`GetPriorityClass <http://msdn.microsoft.com/en-us/library/ms683211(v=vs.85).aspx>`__
and `SetPriorityClass <http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx>`__
@@ -779,12 +785,6 @@ Process class
>>> p.nice(psutil.HIGH_PRIORITY_CLASS)
- Starting from `Python 3.3 <http://bugs.python.org/issue10784>`__ this
- same functionality is available as
- `os.getpriority() <http://docs.python.org/3/library/os.html#os.getpriority>`__
- and
- `os.setpriority() <http://docs.python.org/3/library/os.html#os.setpriority>`__.
-
.. method:: ionice(ioclass=None, value=None)
Get or set
@@ -1225,7 +1225,7 @@ Popen class
:meth:`send_signal() <psutil.Process.send_signal()>`,
:meth:`terminate() <psutil.Process.terminate()>` and
:meth:`kill() <psutil.Process.kill()>`
- so that you don't accidentally terminate another process, fixing
+ so that you can't accidentally terminate another process, fixing
http://bugs.python.org/issue6973.
>>> import psutil
diff --git a/make.bat b/make.bat
index d7c1091b..f860cdfb 100644
--- a/make.bat
+++ b/make.bat
@@ -26,8 +26,16 @@ if "%TSCRIPT%" == "" (
set TSCRIPT=test\test_psutil.py
)
-rem Needed to compile using Mingw.
-set PATH=C:\MinGW\bin;%PATH%
+set PYTHON26=C:\Python26\python.exe
+set PYTHON27=C:\Python27\python.exe
+set PYTHON33=C:\Python33\python.exe
+set PYTHON34=C:\Python34\python.exe
+set PYTHON26-64=C:\Python26-64\python.exe
+set PYTHON27-64=C:\Python27-64\python.exe
+set PYTHON33-64=C:\Python33-64\python.exe
+set PYTHON34-64=C:\Python34-64\python.exe
+
+set ALL_PYTHONS=%PYTHON26% %PYTHON27% %PYTHON33% %PYTHON34% %PYTHON26-64% %PYTHON27-64% %PYTHON33-64% %PYTHON34-64%
rem Needed to locate the .pypirc file and upload exes on PYPI.
set HOME=%USERPROFILE%
@@ -38,10 +46,9 @@ if "%1" == "help" (
:help
echo Run `make ^<target^>` where ^<target^> is one of:
echo build compile without installing
- echo build-exes create exe installers in dist directory
- echo build-wheels create wheel installers in dist directory
echo build-all build exes + wheels
echo clean clean build files
+ echo flake8 run flake8
echo install compile and install
echo setup-env install pip, unittest2, wheels for all python versions
echo test run tests
@@ -49,8 +56,6 @@ if "%1" == "help" (
echo test-process run process related tests
echo test-system run system APIs related tests
echo uninstall uninstall
- echo upload-exes upload exe installers on pypi
- echo upload-wheels upload wheel installers on pypi
echo upload-all upload exes + wheels
goto :eof
)
@@ -70,10 +75,11 @@ if "%1" == "clean" (
if "%1" == "build" (
:build
+ "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat"
%PYTHON% setup.py build
if %errorlevel% neq 0 goto :error
rem copies *.pyd files in ./psutil directory in order to allow
- rem "import psutil" when using the interactive interpreter from
+ rem "import psutil" when using the interactive interpreter from
rem within this directory.
%PYTHON% setup.py build_ext -i
if %errorlevel% neq 0 goto :error
@@ -121,113 +127,75 @@ if "%1" == "test-memleaks" (
goto :eof
)
-if "%1" == "build-exes" (
- :build-exes
- rem "standard" 32 bit versions, using VS 2008 (2.6, 2.7) or VS 2010 (3.3+)
- C:\Python26\python.exe setup.py build bdist_wininst || goto :error
- C:\Python27\python.exe setup.py build bdist_wininst || goto :error
- C:\Python33\python.exe setup.py build bdist_wininst || goto :error
- C:\Python34\python.exe setup.py build bdist_wininst || goto :error
- rem 64 bit versions
- rem Python 2.7 + VS 2008 requires vcvars64.bat to be run first:
- rem http://stackoverflow.com/questions/11072521/
- rem Windows SDK and .NET Framework 3.5 SP1 also need to be installed (sigh)
- "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat"
- C:\Python27-64\python.exe setup.py build bdist_wininst || goto :error
- C:\Python33-64\python.exe setup.py build bdist_wininst || goto :error
- C:\Python34-64\python.exe setup.py build bdist_wininst || goto :error
- echo OK
- goto :eof
-)
-
-if "%1" == "build-wheels" (
- :build-wheels
- C:\Python26\python.exe setup.py build bdist_wheel || goto :error
- C:\Python27\python.exe setup.py build bdist_wheel || goto :error
- C:\Python33\python.exe setup.py build bdist_wheel || goto :error
- C:\Python34\python.exe setup.py build bdist_wheel || goto :error
- rem 64 bit versions
- rem Python 2.7 + VS 2008 requires vcvars64.bat to be run first:
- rem http://stackoverflow.com/questions/11072521/
- rem Windows SDK and .NET Framework 3.5 SP1 also need to be installed (sigh)
- "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat"
- C:\Python27-64\python.exe setup.py build bdist_wheel || goto :error
- C:\Python33-64\python.exe setup.py build bdist_wheel || goto :error
- C:\Python34-64\python.exe setup.py build bdist_wheel || goto :error
- echo OK
- goto :eof
-)
-
if "%1" == "build-all" (
- rem for some reason this needs to be called twice (f**king windows...)
- call :build-exes
- call :build-exes
+ :build-all
+ "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat"
+ for %%P in (%ALL_PYTHONS%) do (
+ @echo ------------------------------------------------
+ @echo building exe for %%P
+ @echo ------------------------------------------------
+ %%P setup.py build bdist_wininst || goto :error
+ @echo ------------------------------------------------
+ @echo building wheel for %%P
+ @echo ------------------------------------------------
+ %%P setup.py build bdist_wheel || goto :error
+ )
echo OK
goto :eof
)
-if "%1" == "upload-exes" (
+if "%1" == "upload-all" (
:upload-exes
- rem "standard" 32 bit versions, using VS 2008 (2.6, 2.7) or VS 2010 (3.3+)
- C:\Python26\python.exe setup.py bdist_wininst upload || goto :error
- C:\Python27\python.exe setup.py bdist_wininst upload || goto :error
- C:\Python33\python.exe setup.py bdist_wininst upload || goto :error
- C:\Python34\python.exe setup.py bdist_wininst upload || goto :error
- rem 64 bit versions
- C:\Python27-64\python.exe setup.py build bdist_wininst upload || goto :error
- C:\Python33-64\python.exe setup.py build bdist_wininst upload || goto :error
- C:\Python34-64\python.exe setup.py build bdist_wininst upload || goto :error
- echo OK
- goto :eof
-)
-
-if "%1" == "upload-wheels" (
- :build-wheels
- C:\Python26\python.exe setup.py build bdist_wheel upload || goto :error
- C:\Python27\python.exe setup.py build bdist_wheel upload || goto :error
- C:\Python33\python.exe setup.py build bdist_wheel upload || goto :error
- C:\Python34\python.exe setup.py build bdist_wheel upload || goto :error
- rem 64 bit versions
- rem Python 2.7 + VS 2008 requires vcvars64.bat to be run first:
- rem http://stackoverflow.com/questions/11072521/
- rem Windows SDK and .NET Framework 3.5 SP1 also need to be installed (sigh)
"C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat"
- C:\Python27-64\python.exe setup.py build bdist_wheel upload || goto :error
- C:\Python33-64\python.exe setup.py build bdist_wheel upload || goto :error
- C:\Python34-64\python.exe setup.py build bdist_wheel upload || goto :error
+ for %%P in (%ALL_PYTHONS%) do (
+ @echo ------------------------------------------------
+ @echo uploading exe for %%P
+ @echo ------------------------------------------------
+ %%P setup.py build bdist_wininst upload || goto :error
+ @echo ------------------------------------------------
+ @echo uploading wheel for %%P
+ @echo ------------------------------------------------
+ %%P setup.py build bdist_wheel upload || goto :error
+ )
echo OK
goto :eof
)
-if "%1" == "upload-all" (
- call :upload-exes
- call :upload-wheels
- echo OK
+if "%1" == "setup-env" (
+ :setup-env
+ @echo ------------------------------------------------
+ @echo downloading pip installer
+ @echo ------------------------------------------------
+ C:\python27\python.exe -c "import urllib2; r = urllib2.urlopen('https://raw.github.com/pypa/pip/master/contrib/get-pip.py'); open('get-pip.py', 'wb').write(r.read())"
+ for %%P in (%ALL_PYTHONS%) do (
+ @echo ------------------------------------------------
+ @echo installing pip for %%P
+ @echo ------------------------------------------------
+ %%P get-pip.py
+ )
+ for %%P in (%ALL_PYTHONS%) do (
+ @echo ------------------------------------------------
+ @echo installing deps for %%P
+ @echo ------------------------------------------------
+ rem mandatory / for unittests
+ %%P -m pip install unittest2 ipaddress mock wmi wheel --upgrade
+ rem nice to have
+ %%P -m pip install ipdb pep8 pyflakes flake8 --upgrade
+ )
goto :eof
)
-if "%1" == "setup-env" (
- echo downloading pip installer
- C:\python27\python.exe -c "import urllib2; url = urllib2.urlopen('https://raw.github.com/pypa/pip/master/contrib/get-pip.py'); data = url.read(); f = open('get-pip.py', 'w'); f.write(data)"
- C:\python26\python.exe get-pip.py & C:\python26\scripts\pip install unittest2 wheel ipaddress --upgrade
- C:\python27\python.exe get-pip.py & C:\python27\scripts\pip install wheel ipaddress --upgrade
- C:\python33\python.exe get-pip.py & C:\python33\scripts\pip install wheel ipaddress --upgrade
- C:\python34\scripts\easy_install.exe wheel
- rem 64-bit versions
- C:\python27-64\python.exe get-pip.py & C:\python27-64\scripts\pip install wheel ipaddress --upgrade
- C:\python33-64\python.exe get-pip.py & C:\python33-64\scripts\pip install wheel ipaddress --upgrade
- C:\python34-64\scripts\easy_install.exe wheel
- rem install ipdb only for py 2.7 and 3.4
- C:\python27\scripts\pip install ipdb --upgrade
- C:\python34\scripts\easy_install.exe ipdb
+if "%1" == "flake8" (
+ :flake8
+ %PYTHON% -c "from flake8.main import main; main()"
goto :eof
)
goto :help
:error
- echo ------------------------------------------------
- echo last command exited with error code %errorlevel%
- echo ------------------------------------------------
- exit /b %errorlevel%
+ @echo ------------------------------------------------
+ @echo last command exited with error code %errorlevel%
+ @echo ------------------------------------------------
+ @exit /b %errorlevel%
goto :eof
diff --git a/psutil/__init__.py b/psutil/__init__.py
index 608db167..7a3fb066 100644
--- a/psutil/__init__.py
+++ b/psutil/__init__.py
@@ -158,7 +158,7 @@ __all__ = [
]
__all__.extend(_psplatform.__extra__all__)
__author__ = "Giampaolo Rodola'"
-__version__ = "3.0.0"
+__version__ = "3.0.2"
version_info = tuple([int(num) for num in __version__.split('.')])
AF_LINK = _psplatform.AF_LINK
_TOTAL_PHYMEM = None
@@ -190,6 +190,12 @@ class Error(Exception):
from this one.
"""
+ def __init__(self, msg=""):
+ self.msg = msg
+
+ def __str__(self):
+ return self.msg
+
class NoSuchProcess(Error):
"""Exception raised when a process with a certain PID doesn't
@@ -197,7 +203,7 @@ class NoSuchProcess(Error):
"""
def __init__(self, pid, name=None, msg=None):
- Error.__init__(self)
+ Error.__init__(self, msg)
self.pid = pid
self.name = name
self.msg = msg
@@ -208,9 +214,6 @@ class NoSuchProcess(Error):
details = "(pid=%s)" % self.pid
self.msg = "process no longer exists " + details
- def __str__(self):
- return self.msg
-
class ZombieProcess(NoSuchProcess):
"""Exception raised when querying a zombie process. This is
@@ -221,7 +224,7 @@ class ZombieProcess(NoSuchProcess):
"""
def __init__(self, pid, name=None, ppid=None, msg=None):
- Error.__init__(self)
+ Error.__init__(self, msg)
self.pid = pid
self.ppid = ppid
self.name = name
@@ -236,15 +239,12 @@ class ZombieProcess(NoSuchProcess):
details = "(pid=%s)" % self.pid
self.msg = "process still exists but it's a zombie " + details
- def __str__(self):
- return self.msg
-
class AccessDenied(Error):
"""Exception raised when permission to perform an action is denied."""
def __init__(self, pid=None, name=None, msg=None):
- Error.__init__(self)
+ Error.__init__(self, msg)
self.pid = pid
self.name = name
self.msg = msg
@@ -256,9 +256,6 @@ class AccessDenied(Error):
else:
self.msg = ""
- def __str__(self):
- return self.msg
-
class TimeoutExpired(Error):
"""Raised on Process.wait(timeout) if timeout expires and process
@@ -266,18 +263,15 @@ class TimeoutExpired(Error):
"""
def __init__(self, seconds, pid=None, name=None):
- Error.__init__(self)
+ Error.__init__(self, "timeout after %s seconds" % seconds)
self.seconds = seconds
self.pid = pid
self.name = name
- self.msg = "timeout after %s seconds" % seconds
if (pid is not None) and (name is not None):
self.msg += " (pid=%s, name=%s)" % (pid, repr(name))
elif (pid is not None):
self.msg += " (pid=%s)" % self.pid
- def __str__(self):
- return self.msg
# push exception classes into platform specific module namespace
_psplatform.NoSuchProcess = NoSuchProcess
@@ -290,6 +284,7 @@ _psplatform.TimeoutExpired = TimeoutExpired
# --- Process class
# =====================================================================
+
def _assert_pid_not_reused(fun):
"""Decorator which raises NoSuchProcess in case a process is no
longer running or its PID has been reused.
@@ -686,7 +681,7 @@ class Process(object):
"""
if ioclass is None:
if value is not None:
- raise ValueError("'ioclass' must be specified")
+ raise ValueError("'ioclass' argument must be specified")
return self._proc.ionice_get()
else:
return self._proc.ionice_set(ioclass, value)
@@ -1012,10 +1007,12 @@ class Process(object):
if _POSIX:
def _send_signal(self, sig):
- # XXX: according to "man 2 kill" PID 0 has a special
- # meaning as it refers to <<every process in the process
- # group of the calling process>>, so should we prevent
- # it here?
+ if self.pid == 0:
+ # see "man 2 kill"
+ raise ValueError(
+ "preventing sending signal to process with PID 0 as it "
+ "would affect every process in the process group of the "
+ "calling process (os.getpid()) instead of PID 0")
try:
os.kill(self.pid, sig)
except OSError as err:
@@ -1105,6 +1102,7 @@ class Process(object):
# --- Popen class
# =====================================================================
+
class Popen(Process):
"""A more convenient interface to stdlib subprocess module.
It starts a sub process and deals with it exactly as when using
@@ -1172,6 +1170,7 @@ class Popen(Process):
# --- system processes related functions
# =====================================================================
+
def pids():
"""Return a list of current running PIDs."""
return _psplatform.pids()
@@ -1338,13 +1337,14 @@ def wait_procs(procs, timeout=None, callback=None):
# --- CPU related functions
# =====================================================================
+
@memoize
def cpu_count(logical=True):
"""Return the number of logical CPUs in the system (same as
os.cpu_count() in Python 3.4).
If logical is False return the number of physical cores only
- (hyper thread CPUs are excluded).
+ (e.g. hyper thread CPUs are excluded).
Return None if undetermined.
@@ -1548,6 +1548,7 @@ def cpu_times_percent(interval=None, percpu=False):
# --- system memory related functions
# =====================================================================
+
def virtual_memory():
"""Return statistics about system memory usage as a namedtuple
including the following fields, expressed in bytes:
@@ -1628,6 +1629,7 @@ def swap_memory():
# --- disks/paritions related functions
# =====================================================================
+
def disk_usage(path):
"""Return disk usage statistics about the given path as a namedtuple
including total, used and free space expressed in bytes plus the
@@ -1682,6 +1684,7 @@ def disk_io_counters(perdisk=False):
# --- network related functions
# =====================================================================
+
def net_io_counters(pernic=False):
"""Return network I/O statistics as a namedtuple including
the following fields:
@@ -1852,14 +1855,6 @@ def test():
time.localtime(sum(pinfo['cpu_times'])))
try:
user = p.username()
- except KeyError:
- if _POSIX:
- if pinfo['uids']:
- user = str(pinfo['uids'].real)
- else:
- user = ''
- else:
- raise
except Error:
user = ''
if _WINDOWS and '\\' in user:
diff --git a/psutil/_common.py b/psutil/_common.py
index 132d9d59..e9acf595 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -12,17 +12,19 @@ import functools
import os
import socket
import stat
+import sys
+from collections import namedtuple
+from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM
try:
import threading
except ImportError:
import dummy_threading as threading
-try:
- import enum # py >= 3.4
-except ImportError:
+
+if sys.version_info >= (3, 4):
+ import enum
+else:
enum = None
-from collections import namedtuple
-from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM
# --- constants
diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py
index 3ce5fd1c..db54a02e 100644
--- a/psutil/_psbsd.py
+++ b/psutil/_psbsd.py
@@ -143,7 +143,11 @@ def cpu_count_physical():
if index != -1:
s = s[:index + 9]
root = ET.fromstring(s)
- ret = len(root.findall('group/children/group/cpu')) or None
+ try:
+ ret = len(root.findall('group/children/group/cpu')) or None
+ finally:
+ # needed otherwise it will memleak
+ root.clear()
if not ret:
# If logical CPUs are 1 it's obvious we'll have only 1
# physical CPU.
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 40132794..be443eff 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -25,7 +25,7 @@ from . import _psutil_linux as cext
from . import _psutil_posix as cext_posix
from ._common import isfile_strict, usage_percent
from ._common import NIC_DUPLEX_FULL, NIC_DUPLEX_HALF, NIC_DUPLEX_UNKNOWN
-from ._compat import PY3
+from ._compat import PY3, long
if sys.version_info >= (3, 4):
import enum
@@ -40,10 +40,7 @@ __extra__all__ = [
# connection status constants
"CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
"CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
- "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING",
- # other
- "phymem_buffers", "cached_phymem"]
-
+ "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", ]
# --- constants
@@ -279,14 +276,28 @@ def cpu_count_logical():
def cpu_count_physical():
- """Return the number of physical CPUs in the system."""
+ """Return the number of physical cores in the system."""
+ mapping = {}
+ current_info = {}
with open('/proc/cpuinfo', 'rb') as f:
- found = set()
for line in f:
- if line.lower().startswith(b'physical id'):
- found.add(line.strip())
+ line = line.strip().lower()
+ if not line:
+ # new section
+ if (b'physical id' in current_info and
+ b'cpu cores' in current_info):
+ mapping[current_info[b'physical id']] = \
+ current_info[b'cpu cores']
+ current_info = {}
+ else:
+ # ongoing section
+ if (line.startswith(b'physical id') or
+ line.startswith(b'cpu cores')):
+ key, value = line.split(b'\t:', 1)
+ current_info[key] = int(value)
+
# mimic os.cpu_count()
- return len(found) if found else None
+ return sum(mapping.values()) or None
# --- other system functions
@@ -318,7 +329,7 @@ def boot_time():
ret = float(line.strip().split()[1])
BOOT_TIME = ret
return ret
- raise RuntimeError("line 'btime' not found")
+ raise RuntimeError("line 'btime' not found in /proc/stat")
# --- processes
@@ -372,9 +383,17 @@ class Connections:
for fd in os.listdir("/proc/%s/fd" % pid):
try:
inode = os.readlink("/proc/%s/fd/%s" % (pid, fd))
- except OSError:
- # TODO: need comment here
- continue
+ except OSError as err:
+ # ENOENT == file which is gone in the meantime;
+ # os.stat('/proc/%s' % self.pid) will be done later
+ # to force NSP (if it's the case)
+ if err.errno in (errno.ENOENT, errno.ESRCH):
+ continue
+ elif err.errno == errno.EINVAL:
+ # not a link
+ continue
+ else:
+ raise
else:
if inode.startswith('socket:['):
# the process is using a socket
@@ -455,8 +474,13 @@ class Connections:
with open(file, 'rt') as f:
f.readline() # skip the first line
for line in f:
- _, laddr, raddr, status, _, _, _, _, _, inode = \
- line.split()[:10]
+ try:
+ _, laddr, raddr, status, _, _, _, _, _, inode = \
+ line.split()[:10]
+ except ValueError:
+ raise RuntimeError(
+ "error while parsing %s; malformed line %r" % (
+ file, line))
if inode in inodes:
# # We assume inet sockets are unique, so we error
# # out if there are multiple references to the
@@ -484,7 +508,12 @@ class Connections:
f.readline() # skip the first line
for line in f:
tokens = line.split()
- _, _, _, _, type_, _, inode = tokens[0:7]
+ try:
+ _, _, _, _, type_, _, inode = tokens[0:7]
+ except ValueError:
+ raise RuntimeError(
+ "error while parsing %s; malformed line %r" % (
+ file, line))
if inode in inodes:
# With UNIX sockets we can have a single inode
# referencing many file descriptors.
@@ -716,13 +745,14 @@ class Process(object):
fname = "/proc/%s/stat" % self.pid
kw = dict(encoding=DEFAULT_ENCODING) if PY3 else dict()
with open(fname, "rt", **kw) as f:
- # XXX - gets changed later and probably needs refactoring
- return f.read().split(' ')[1].replace('(', '').replace(')', '')
+ data = f.read()
+ # XXX - gets changed later and probably needs refactoring
+ return data[data.find('(') + 1:data.rfind(')')]
def exe(self):
try:
exe = os.readlink("/proc/%s/exe" % self.pid)
- except (OSError, IOError) as err:
+ except OSError as err:
if err.errno in (errno.ENOENT, errno.ESRCH):
# no such file error; might be raised also if the
# path actually exists for system processes with
@@ -753,7 +783,10 @@ class Process(object):
fname = "/proc/%s/cmdline" % self.pid
kw = dict(encoding=DEFAULT_ENCODING) if PY3 else dict()
with open(fname, "rt", **kw) as f:
- return [x for x in f.read().split('\x00') if x]
+ data = f.read()
+ if data.endswith('\x00'):
+ data = data[:-1]
+ return [x for x in data.split('\x00')]
@wrap_exceptions
def terminal(self):
@@ -961,7 +994,7 @@ class Process(object):
try:
with open(fname, 'rb') as f:
st = f.read().strip()
- except EnvironmentError as err:
+ except IOError as err:
if err.errno == errno.ENOENT:
# no such file or directory; it means thread
# disappeared on us
@@ -1022,32 +1055,43 @@ class Process(object):
@wrap_exceptions
def ionice_set(self, ioclass, value):
+ if value is not None:
+ if not PY3 and not isinstance(value, (int, long)):
+ msg = "value argument is not an integer (gor %r)" % value
+ raise TypeError(msg)
+ if not 0 <= value <= 8:
+ raise ValueError(
+ "value argument range expected is between 0 and 8")
+
if ioclass in (IOPRIO_CLASS_NONE, None):
if value:
- msg = "can't specify value with IOPRIO_CLASS_NONE"
+ msg = "can't specify value with IOPRIO_CLASS_NONE " \
+ "(got %r)" % value
raise ValueError(msg)
ioclass = IOPRIO_CLASS_NONE
value = 0
- if ioclass in (IOPRIO_CLASS_RT, IOPRIO_CLASS_BE):
- if value is None:
- value = 4
elif ioclass == IOPRIO_CLASS_IDLE:
if value:
- msg = "can't specify value with IOPRIO_CLASS_IDLE"
+ msg = "can't specify value with IOPRIO_CLASS_IDLE " \
+ "(got %r)" % value
raise ValueError(msg)
value = 0
+ elif ioclass in (IOPRIO_CLASS_RT, IOPRIO_CLASS_BE):
+ if value is None:
+ # TODO: add comment explaining why this is 4 (?)
+ value = 4
else:
- value = 0
- if not 0 <= value <= 8:
- raise ValueError(
- "value argument range expected is between 0 and 8")
+ # otherwise we would get OSError(EVINAL)
+ raise ValueError("invalid ioclass argument %r" % ioclass)
+
return cext.proc_ioprio_set(self.pid, ioclass, value)
if HAS_PRLIMIT:
@wrap_exceptions
def rlimit(self, resource, limits=None):
- # if pid is 0 prlimit() applies to the calling process and
- # we don't want that
+ # If pid is 0 prlimit() applies to the calling process and
+ # we don't want that. We should never get here though as
+ # PID 0 is not supported on Linux.
if self.pid == 0:
raise ValueError("can't use prlimit() against PID 0 process")
try:
@@ -1058,7 +1102,8 @@ class Process(object):
# set
if len(limits) != 2:
raise ValueError(
- "second argument must be a (soft, hard) tuple")
+ "second argument must be a (soft, hard) tuple, "
+ "got %s" % repr(limits))
soft, hard = limits
cext.linux_prlimit(self.pid, resource, soft, hard)
except OSError as err:
@@ -1126,27 +1171,30 @@ class Process(object):
@wrap_exceptions
def ppid(self):
- with open("/proc/%s/status" % self.pid, 'rb') as f:
+ fpath = "/proc/%s/status" % self.pid
+ with open(fpath, 'rb') as f:
for line in f:
if line.startswith(b"PPid:"):
# PPid: nnnn
return int(line.split()[1])
- raise NotImplementedError("line not found")
+ raise NotImplementedError("line 'PPid' not found in %s" % fpath)
@wrap_exceptions
def uids(self):
- with open("/proc/%s/status" % self.pid, 'rb') as f:
+ fpath = "/proc/%s/status" % self.pid
+ with open(fpath, 'rb') as f:
for line in f:
if line.startswith(b'Uid:'):
_, real, effective, saved, fs = line.split()
return _common.puids(int(real), int(effective), int(saved))
- raise NotImplementedError("line not found")
+ raise NotImplementedError("line 'Uid' not found in %s" % fpath)
@wrap_exceptions
def gids(self):
- with open("/proc/%s/status" % self.pid, 'rb') as f:
+ fpath = "/proc/%s/status" % self.pid
+ with open(fpath, 'rb') as f:
for line in f:
if line.startswith(b'Gid:'):
_, real, effective, saved, fs = line.split()
return _common.pgids(int(real), int(effective), int(saved))
- raise NotImplementedError("line not found")
+ raise NotImplementedError("line 'Gid' not found in %s" % fpath)
diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py
index bb47fd29..bc35a718 100644
--- a/psutil/_pssunos.py
+++ b/psutil/_pssunos.py
@@ -96,7 +96,9 @@ def swap_memory():
# usr/src/cmd/swap/swap.c
# ...nevertheless I can't manage to obtain the same numbers as 'swap'
# cmdline utility, so let's parse its output (sigh!)
- p = subprocess.Popen(['swap', '-l', '-k'], stdout=subprocess.PIPE)
+ p = subprocess.Popen(['/usr/bin/env', 'PATH=/usr/sbin:/sbin:%s' %
+ os.environ['PATH'], 'swap', '-l', '-k'],
+ stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
if PY3:
stdout = stdout.decode(sys.stdout.encoding)
diff --git a/psutil/_psutil_sunos.c b/psutil/_psutil_sunos.c
index fb80e3ef..0cb6978f 100644
--- a/psutil/_psutil_sunos.c
+++ b/psutil/_psutil_sunos.c
@@ -736,18 +736,18 @@ psutil_net_io_counters(PyObject *self, PyObject *args)
#if defined(_INT64_TYPE)
py_ifc_info = Py_BuildValue("(KKKKkkii)",
- rbytes->value.ui64,
wbytes->value.ui64,
- rpkts->value.ui64,
+ rbytes->value.ui64,
wpkts->value.ui64,
+ rpkts->value.ui64,
ierrs->value.ui32,
oerrs->value.ui32,
#else
py_ifc_info = Py_BuildValue("(kkkkkkii)",
- rbytes->value.ui32,
wbytes->value.ui32,
- rpkts->value.ui32,
+ rbytes->value.ui32,
wpkts->value.ui32,
+ rpkts->value.ui32,
ierrs->value.ui32,
oerrs->value.ui32,
#endif
diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c
index 9a4c0944..b05db820 100644
--- a/psutil/_psutil_windows.c
+++ b/psutil/_psutil_windows.c
@@ -2683,6 +2683,19 @@ psutil_proc_num_handles(PyObject *self, PyObject *args)
}
+/*
+ * Get various process information by using NtQuerySystemInformation.
+ * We use this as a fallback when faster functions fail with access
+ * denied. This is slower because it iterates over all processes.
+ * Returned tuple includes the following process info:
+ *
+ * - num_threads
+ * - ctx_switches
+ * - num_handles (fallback)
+ * - user/kernel times (fallback)
+ * - create time (fallback)
+ * - io counters (fallback)
+ */
static PyObject *
psutil_proc_info(PyObject *self, PyObject *args)
{
@@ -3180,7 +3193,7 @@ PsutilMethods[] =
"seconds since the epoch"},
{"proc_memory_info", psutil_proc_memory_info, METH_VARARGS,
"Return a tuple of process memory information"},
- {"proc_memory_info_2", psutil_proc_memory_info, METH_VARARGS,
+ {"proc_memory_info_2", psutil_proc_memory_info_2, METH_VARARGS,
"Alternate implementation"},
{"proc_cwd", psutil_proc_cwd, METH_VARARGS,
"Return process current working directory"},
@@ -3387,4 +3400,4 @@ void init_psutil_windows(void)
#if PY_MAJOR_VERSION >= 3
return module;
#endif
-} \ No newline at end of file
+}
diff --git a/psutil/arch/windows/process_info.c b/psutil/arch/windows/process_info.c
index ad272bff..a59cce47 100644
--- a/psutil/arch/windows/process_info.c
+++ b/psutil/arch/windows/process_info.c
@@ -357,7 +357,10 @@ const int STATUS_BUFFER_TOO_SMALL = 0xC0000023L;
/*
* Given a process PID and a PSYSTEM_PROCESS_INFORMATION structure
- * fills the structure with process information.
+ * fills the structure with various process information by using
+ * NtQuerySystemInformation.
+ * We use this as a fallback when faster functions fail with access
+ * denied. This is slower because it iterates over all processes.
* On success return 1, else 0 with Python exception already set.
*/
int
diff --git a/test/README b/test/README
deleted file mode 100644
index 801f93d1..00000000
--- a/test/README
+++ /dev/null
@@ -1,15 +0,0 @@
-- The recommended way to run tests (also on Windows) is to cd into parent
- directory and run:
-
- make test
-
-- If you're on Python < 2.7 unittest2 module must be installed first:
- https://pypi.python.org/pypi/unittest2
-
-- The main test script is test_psutil.py, which also imports platform-specific
- _*.py scripts (which should be ignored).
-
-- test_memory_leaks.py looks for memory leaks into C extension modules and must
- be run separately with:
-
- make memtest
diff --git a/test/README.rst b/test/README.rst
new file mode 100644
index 00000000..3f2a468e
--- /dev/null
+++ b/test/README.rst
@@ -0,0 +1,21 @@
+- The recommended way to run tests (also on Windows) is to cd into parent
+ directory and run ``make test``
+
+- Dependencies for running tests:
+ - python 2.6: ipaddress, mock, unittest2
+ - python 2.7: ipaddress, mock
+ - python 3.2: ipaddress, mock
+ - python 3.3: ipaddress
+ - python >= 3.4: no deps required
+
+- The main test script is ``test_psutil.py``, which also imports platform-specific
+ ``_*.py`` scripts (which should be ignored).
+
+- ``test_memory_leaks.py`` looks for memory leaks into C extension modules and must
+ be run separately with ``make test-memleaks``.
+
+- To run tests on all supported Python version install tox (pip install tox)
+ then run ``tox``.
+
+- Every time a commit is pushed tests are automatically run on Travis:
+ https://travis-ci.org/giampaolo/psutil/
diff --git a/test/_bsd.py b/test/_bsd.py
index 9dfeb493..e4a3225d 100644
--- a/test/_bsd.py
+++ b/test/_bsd.py
@@ -16,7 +16,7 @@ import time
import psutil
from psutil._compat import PY3
-from test_psutil import (TOLERANCE, sh, get_test_subprocess, which,
+from test_psutil import (TOLERANCE, BSD, sh, get_test_subprocess, which,
retry_before_failing, reap_children, unittest)
@@ -50,6 +50,7 @@ def muse(field):
return int(line.split()[1])
+@unittest.skipUnless(BSD, "not a BSD system")
class BSDSpecificTestCase(unittest.TestCase):
@classmethod
@@ -186,6 +187,10 @@ class BSDSpecificTestCase(unittest.TestCase):
self.assertAlmostEqual(psutil.virtual_memory().buffers, syst,
delta=TOLERANCE)
+ def test_cpu_count_logical(self):
+ syst = sysctl("hw.ncpu")
+ self.assertEqual(psutil.cpu_count(logical=True), syst)
+
# --- virtual_memory(); tests against muse
@unittest.skipUnless(MUSE_AVAILABLE, "muse cmdline tool is not available")
@@ -236,12 +241,12 @@ class BSDSpecificTestCase(unittest.TestCase):
delta=TOLERANCE)
-def test_main():
+def main():
test_suite = unittest.TestSuite()
test_suite.addTest(unittest.makeSuite(BSDSpecificTestCase))
result = unittest.TextTestRunner(verbosity=2).run(test_suite)
return result.wasSuccessful()
if __name__ == '__main__':
- if not test_main():
+ if not main():
sys.exit(1)
diff --git a/test/_linux.py b/test/_linux.py
index 1ba8fb94..493c1491 100644
--- a/test/_linux.py
+++ b/test/_linux.py
@@ -7,8 +7,8 @@
"""Linux specific tests. These are implicitly run by test_psutil.py."""
from __future__ import division
-import array
import contextlib
+import errno
import fcntl
import os
import pprint
@@ -16,14 +16,22 @@ import re
import socket
import struct
import sys
+import tempfile
import time
+import warnings
-from test_psutil import POSIX, TOLERANCE, TRAVIS
+try:
+ from unittest import mock # py3
+except ImportError:
+ import mock # requires "pip install mock"
+
+from test_psutil import POSIX, TOLERANCE, TRAVIS, LINUX
from test_psutil import (skip_on_not_implemented, sh, get_test_subprocess,
retry_before_failing, get_kernel_version, unittest,
- which)
+ which, call_until)
import psutil
+import psutil._pslinux
from psutil._compat import PY3
@@ -61,6 +69,7 @@ def get_mac_address(ifname):
return ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1]
+@unittest.skipUnless(LINUX, "not a Linux system")
class LinuxSpecificTestCase(unittest.TestCase):
@unittest.skipIf(
@@ -205,6 +214,149 @@ class LinuxSpecificTestCase(unittest.TestCase):
self.assertEqual(len(nics), found, msg="%s\n---\n%s" % (
pprint.pformat(nics), out))
+ @unittest.skipUnless(which("nproc"), "nproc utility not available")
+ def test_cpu_count_logical_w_nproc(self):
+ num = int(sh("nproc --all"))
+ self.assertEqual(psutil.cpu_count(logical=True), num)
+
+ @unittest.skipUnless(which("lscpu"), "lscpu utility not available")
+ def test_cpu_count_logical_w_lscpu(self):
+ out = sh("lscpu -p")
+ num = len([x for x in out.split('\n') if not x.startswith('#')])
+ self.assertEqual(psutil.cpu_count(logical=True), num)
+
+ # --- mocked tests
+
+ def test_virtual_memory_mocked_warnings(self):
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ with warnings.catch_warnings(record=True) as ws:
+ warnings.simplefilter("always")
+ ret = psutil._pslinux.virtual_memory()
+ assert m.called
+ self.assertEqual(len(ws), 1)
+ w = ws[0]
+ self.assertTrue(w.filename.endswith('psutil/_pslinux.py'))
+ self.assertIn(
+ "'cached', 'active' and 'inactive' memory stats couldn't "
+ "be determined", str(w.message))
+ self.assertEqual(ret.cached, 0)
+ self.assertEqual(ret.active, 0)
+ self.assertEqual(ret.inactive, 0)
+
+ def test_swap_memory_mocked_warnings(self):
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ with warnings.catch_warnings(record=True) as ws:
+ warnings.simplefilter("always")
+ ret = psutil._pslinux.swap_memory()
+ assert m.called
+ self.assertEqual(len(ws), 1)
+ w = ws[0]
+ self.assertTrue(w.filename.endswith('psutil/_pslinux.py'))
+ self.assertIn(
+ "'sin' and 'sout' swap memory stats couldn't "
+ "be determined", str(w.message))
+ self.assertEqual(ret.sin, 0)
+ self.assertEqual(ret.sout, 0)
+
+ def test_cpu_count_logical_mocked(self):
+ import psutil._pslinux
+ original = psutil._pslinux.cpu_count_logical()
+ with mock.patch(
+ 'psutil._pslinux.os.sysconf', side_effect=ValueError) as m:
+ # Here we want to mock os.sysconf("SC_NPROCESSORS_ONLN") in
+ # order to test /proc/cpuinfo parsing.
+ # We might also test /proc/stat parsing but mocking open()
+ # like that is too difficult.
+ self.assertEqual(psutil._pslinux.cpu_count_logical(), original)
+ assert m.called
+ # Have open() return emtpy data and make sure None is returned
+ # ('cause we want to mimick os.cpu_count())
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ self.assertIsNone(psutil._pslinux.cpu_count_logical())
+ assert m.called
+
+ def test_cpu_count_physical_mocked(self):
+ # Have open() return emtpy data and make sure None is returned
+ # ('cause we want to mimick os.cpu_count())
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ self.assertIsNone(psutil._pslinux.cpu_count_physical())
+ assert m.called
+
+ def test_proc_open_files_file_gone(self):
+ # simulates a file which gets deleted during open_files()
+ # execution
+ p = psutil.Process()
+ files = p.open_files()
+ with tempfile.NamedTemporaryFile():
+ # give the kernel some time to see the new file
+ call_until(p.open_files, "len(ret) != %i" % len(files))
+ with mock.patch('psutil._pslinux.os.readlink',
+ side_effect=OSError(errno.ENOENT, "")) as m:
+ files = p.open_files()
+ assert not files
+ assert m.called
+ # also simulate the case where os.readlink() returns EINVAL
+ # in which case psutil is supposed to 'continue'
+ with mock.patch('psutil._pslinux.os.readlink',
+ side_effect=OSError(errno.EINVAL, "")) as m:
+ self.assertEqual(p.open_files(), [])
+ assert m.called
+
+ def test_proc_terminal_mocked(self):
+ with mock.patch('psutil._pslinux._psposix._get_terminal_map',
+ return_value={}) as m:
+ self.assertIsNone(psutil._pslinux.Process(os.getpid()).terminal())
+ assert m.called
+
+ def test_proc_num_ctx_switches_mocked(self):
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ self.assertRaises(
+ NotImplementedError,
+ psutil._pslinux.Process(os.getpid()).num_ctx_switches)
+ assert m.called
+
+ def test_proc_num_threads_mocked(self):
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ self.assertRaises(
+ NotImplementedError,
+ psutil._pslinux.Process(os.getpid()).num_threads)
+ assert m.called
+
+ def test_proc_ppid_mocked(self):
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ self.assertRaises(
+ NotImplementedError,
+ psutil._pslinux.Process(os.getpid()).ppid)
+ assert m.called
+
+ def test_proc_uids_mocked(self):
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ self.assertRaises(
+ NotImplementedError,
+ psutil._pslinux.Process(os.getpid()).uids)
+ assert m.called
+
+ def test_proc_gids_mocked(self):
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ self.assertRaises(
+ NotImplementedError,
+ psutil._pslinux.Process(os.getpid()).gids)
+ assert m.called
+
+ def test_proc_io_counters_mocked(self):
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ self.assertRaises(
+ NotImplementedError,
+ psutil._pslinux.Process(os.getpid()).io_counters)
+ assert m.called
+
+ def test_boot_time_mocked(self):
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ self.assertRaises(
+ RuntimeError,
+ psutil._pslinux.boot_time)
+ assert m.called
+
# --- tests for specific kernel versions
@unittest.skipUnless(
@@ -241,12 +393,12 @@ class LinuxSpecificTestCase(unittest.TestCase):
self.assertTrue(hasattr(psutil, "RLIMIT_SIGPENDING"))
-def test_main():
+def main():
test_suite = unittest.TestSuite()
test_suite.addTest(unittest.makeSuite(LinuxSpecificTestCase))
result = unittest.TextTestRunner(verbosity=2).run(test_suite)
return result.wasSuccessful()
if __name__ == '__main__':
- if not test_main():
+ if not main():
sys.exit(1)
diff --git a/test/_osx.py b/test/_osx.py
index 784f00e0..6e6e4380 100644
--- a/test/_osx.py
+++ b/test/_osx.py
@@ -15,8 +15,8 @@ import time
import psutil
from psutil._compat import PY3
-from test_psutil import (TOLERANCE, sh, get_test_subprocess, reap_children,
- retry_before_failing, unittest)
+from test_psutil import (TOLERANCE, OSX, sh, get_test_subprocess,
+ reap_children, retry_before_failing, unittest)
PAGESIZE = os.sysconf("SC_PAGE_SIZE")
@@ -47,6 +47,7 @@ def vm_stat(field):
return int(re.search('\d+', line).group(0)) * PAGESIZE
+@unittest.skipUnless(OSX, "not an OSX system")
class OSXSpecificTestCase(unittest.TestCase):
@classmethod
@@ -148,12 +149,12 @@ class OSXSpecificTestCase(unittest.TestCase):
self.assertEqual(tot1, tot2)
-def test_main():
+def main():
test_suite = unittest.TestSuite()
test_suite.addTest(unittest.makeSuite(OSXSpecificTestCase))
result = unittest.TextTestRunner(verbosity=2).run(test_suite)
return result.wasSuccessful()
if __name__ == '__main__':
- if not test_main():
+ if not main():
sys.exit(1)
diff --git a/test/_posix.py b/test/_posix.py
index 4439f2c7..2a263a3f 100644
--- a/test/_posix.py
+++ b/test/_posix.py
@@ -15,7 +15,7 @@ import time
import psutil
from psutil._compat import PY3, callable
-from test_psutil import LINUX, SUNOS, OSX, BSD, PYTHON
+from test_psutil import LINUX, SUNOS, OSX, BSD, PYTHON, POSIX
from test_psutil import (get_test_subprocess, skip_on_access_denied,
retry_before_failing, reap_children, sh, unittest,
get_kernel_version, wait_for_pid)
@@ -42,6 +42,7 @@ def ps(cmd):
return output
+@unittest.skipUnless(POSIX, "not a POSIX system")
class PosixSpecificTestCase(unittest.TestCase):
"""Compare psutil results against 'ps' command line utility."""
@@ -243,12 +244,12 @@ class PosixSpecificTestCase(unittest.TestCase):
self.fail('\n' + '\n'.join(failures))
-def test_main():
+def main():
test_suite = unittest.TestSuite()
test_suite.addTest(unittest.makeSuite(PosixSpecificTestCase))
result = unittest.TextTestRunner(verbosity=2).run(test_suite)
return result.wasSuccessful()
if __name__ == '__main__':
- if not test_main():
+ if not main():
sys.exit(1)
diff --git a/test/_sunos.py b/test/_sunos.py
index 7fdc50b6..3d54ccd8 100644
--- a/test/_sunos.py
+++ b/test/_sunos.py
@@ -7,15 +7,17 @@
"""Sun OS specific tests. These are implicitly run by test_psutil.py."""
import sys
+import os
-from test_psutil import sh, unittest
+from test_psutil import SUNOS, sh, unittest
import psutil
+@unittest.skipUnless(SUNOS, "not a SunOS system")
class SunOSSpecificTestCase(unittest.TestCase):
def test_swap_memory(self):
- out = sh('swap -l -k')
+ out = sh('env PATH=/usr/sbin:/sbin:%s swap -l -k' % os.environ['PATH'])
lines = out.strip().split('\n')[1:]
if not lines:
raise ValueError('no swap device(s) configured')
@@ -35,12 +37,12 @@ class SunOSSpecificTestCase(unittest.TestCase):
self.assertEqual(psutil_swap.free, free)
-def test_main():
+def main():
test_suite = unittest.TestSuite()
test_suite.addTest(unittest.makeSuite(SunOSSpecificTestCase))
result = unittest.TextTestRunner(verbosity=2).run(test_suite)
return result.wasSuccessful()
if __name__ == '__main__':
- if not test_main():
+ if not main():
sys.exit(1)
diff --git a/test/_windows.py b/test/_windows.py
index a3699398..a0a22052 100644
--- a/test/_windows.py
+++ b/test/_windows.py
@@ -15,7 +15,7 @@ import sys
import time
import traceback
-from test_psutil import (get_test_subprocess, reap_children, unittest)
+from test_psutil import WINDOWS, get_test_subprocess, reap_children, unittest
try:
import wmi
@@ -28,16 +28,18 @@ except ImportError:
win32api = win32con = None
from psutil._compat import PY3, callable, long
-from psutil._pswindows import ACCESS_DENIED_SET
-import psutil._psutil_windows as _psutil_windows
import psutil
+cext = psutil._psplatform.cext
+
+
def wrap_exceptions(fun):
def wrapper(self, *args, **kwargs):
try:
return fun(self, *args, **kwargs)
except OSError as err:
+ from psutil._pswindows import ACCESS_DENIED_SET
if err.errno in ACCESS_DENIED_SET:
raise psutil.AccessDenied(None, None)
if err.errno == errno.ESRCH:
@@ -46,6 +48,7 @@ def wrap_exceptions(fun):
return wrapper
+@unittest.skipUnless(WINDOWS, "not a Windows system")
class WindowsSpecificTestCase(unittest.TestCase):
@classmethod
@@ -281,6 +284,7 @@ class WindowsSpecificTestCase(unittest.TestCase):
self.fail('\n' + '\n'.join(failures))
+@unittest.skipUnless(WINDOWS, "not a Windows system")
class TestDualProcessImplementation(unittest.TestCase):
fun_names = [
# function name, tolerance
@@ -324,7 +328,7 @@ class TestDualProcessImplementation(unittest.TestCase):
failures = []
for p in psutil.process_iter():
try:
- nt = ntpinfo(*_psutil_windows.proc_info(p.pid))
+ nt = ntpinfo(*cext.proc_info(p.pid))
except psutil.NoSuchProcess:
continue
assert_ge_0(nt)
@@ -334,7 +338,7 @@ class TestDualProcessImplementation(unittest.TestCase):
continue
if name == 'proc_create_time' and p.pid in (0, 4):
continue
- meth = wrap_exceptions(getattr(_psutil_windows, name))
+ meth = wrap_exceptions(getattr(cext, name))
try:
ret = meth(p.pid)
except (psutil.NoSuchProcess, psutil.AccessDenied):
@@ -356,7 +360,7 @@ class TestDualProcessImplementation(unittest.TestCase):
compare_with_tolerance(ret[3], nt.io_wbytes, tolerance)
elif name == 'proc_memory_info':
try:
- rawtupl = _psutil_windows.proc_memory_info_2(p.pid)
+ rawtupl = cext.proc_memory_info_2(p.pid)
except psutil.NoSuchProcess:
continue
compare_with_tolerance(ret, rawtupl, tolerance)
@@ -385,7 +389,7 @@ class TestDualProcessImplementation(unittest.TestCase):
# process no longer exists
ZOMBIE_PID = max(psutil.pids()) + 5000
for name, _ in self.fun_names:
- meth = wrap_exceptions(getattr(_psutil_windows, name))
+ meth = wrap_exceptions(getattr(cext, name))
self.assertRaises(psutil.NoSuchProcess, meth, ZOMBIE_PID)
diff --git a/test/test_memory_leaks.py b/test/test_memory_leaks.py
index 5a31ac1f..6f02dc0a 100644
--- a/test/test_memory_leaks.py
+++ b/test/test_memory_leaks.py
@@ -10,6 +10,7 @@ functions many times and compare process memory usage before and
after the calls. It might produce false positives.
"""
+import functools
import gc
import os
import socket
@@ -20,7 +21,7 @@ import time
import psutil
import psutil._common
-from psutil._compat import xrange
+from psutil._compat import xrange, callable
from test_psutil import (WINDOWS, POSIX, OSX, LINUX, SUNOS, BSD, TESTFN,
RLIMIT_SUPPORT, TRAVIS)
from test_psutil import (reap_children, supports_ipv6, safe_remove,
@@ -92,7 +93,7 @@ class Base(unittest.TestCase):
def get_mem(self):
return psutil.Process().memory_info()[0]
- def call(self, *args, **kwargs):
+ def call(self, function, *args, **kwargs):
raise NotImplementedError("must be implemented in subclass")
@@ -106,15 +107,25 @@ class TestProcessObjectLeaks(Base):
reap_children()
def call(self, function, *args, **kwargs):
- meth = getattr(self.proc, function)
- if '_exc' in kwargs:
- exc = kwargs.pop('_exc')
- self.assertRaises(exc, meth, *args, **kwargs)
+ if callable(function):
+ if '_exc' in kwargs:
+ exc = kwargs.pop('_exc')
+ self.assertRaises(exc, function, *args, **kwargs)
+ else:
+ try:
+ function(*args, **kwargs)
+ except psutil.Error:
+ pass
else:
- try:
- meth(*args, **kwargs)
- except psutil.Error:
- pass
+ meth = getattr(self.proc, function)
+ if '_exc' in kwargs:
+ exc = kwargs.pop('_exc')
+ self.assertRaises(exc, meth, *args, **kwargs)
+ else:
+ try:
+ meth(*args, **kwargs)
+ except psutil.Error:
+ pass
@skip_if_linux()
def test_name(self):
@@ -165,8 +176,10 @@ class TestProcessObjectLeaks(Base):
value = psutil.Process().ionice()
self.execute('ionice', value)
else:
+ from psutil._pslinux import cext
self.execute('ionice', psutil.IOPRIO_CLASS_NONE)
- self.execute_w_exc(OSError, 'ionice', -1)
+ fun = functools.partial(cext.proc_ioprio_set, os.getpid(), -1, 0)
+ self.execute_w_exc(OSError, fun)
@unittest.skipIf(OSX or SUNOS, "feature not supported on this platform")
@skip_if_linux()
@@ -417,7 +430,7 @@ class TestModuleFunctionsLeaks(Base):
self.execute('net_if_stats')
-def test_main():
+def main():
test_suite = unittest.TestSuite()
tests = [TestProcessObjectLeaksZombie,
TestProcessObjectLeaks,
@@ -428,5 +441,5 @@ def test_main():
return result.wasSuccessful()
if __name__ == '__main__':
- if not test_main():
+ if not main():
sys.exit(1)
diff --git a/test/test_psutil.py b/test/test_psutil.py
index b53ff680..91423841 100644
--- a/test/test_psutil.py
+++ b/test/test_psutil.py
@@ -22,6 +22,7 @@ import contextlib
import datetime
import errno
import functools
+import imp
import json
import os
import pickle
@@ -45,6 +46,10 @@ try:
import ipaddress # python >= 3.3
except ImportError:
ipaddress = None
+try:
+ from unittest import mock # py3
+except ImportError:
+ import mock # requires "pip install mock"
import psutil
from psutil._compat import PY3, callable, long, unicode
@@ -579,6 +584,7 @@ class TestSystemAPIs(unittest.TestCase):
sproc3 = get_test_subprocess()
procs = [psutil.Process(x.pid) for x in (sproc1, sproc2, sproc3)]
self.assertRaises(ValueError, psutil.wait_procs, procs, timeout=-1)
+ self.assertRaises(TypeError, psutil.wait_procs, procs, callback=1)
t = time.time()
gone, alive = psutil.wait_procs(procs, timeout=0.01, callback=callback)
@@ -674,6 +680,8 @@ class TestSystemAPIs(unittest.TestCase):
self.assertFalse(psutil.pid_exists(sproc.pid))
self.assertFalse(psutil.pid_exists(-1))
self.assertEqual(psutil.pid_exists(0), 0 in psutil.pids())
+ # pid 0
+ psutil.pid_exists(0) == 0 in psutil.pids()
def test_pid_exists_2(self):
reap_children()
@@ -712,10 +720,11 @@ class TestSystemAPIs(unittest.TestCase):
self.assertEqual(logical, len(psutil.cpu_times(percpu=True)))
self.assertGreaterEqual(logical, 1)
#
- with open("/proc/cpuinfo") as fd:
- cpuinfo_data = fd.read()
- if "physical id" not in cpuinfo_data:
- raise unittest.SkipTest("cpuinfo doesn't include physical id")
+ if LINUX:
+ with open("/proc/cpuinfo") as fd:
+ cpuinfo_data = fd.read()
+ if "physical id" not in cpuinfo_data:
+ raise unittest.SkipTest("cpuinfo doesn't include physical id")
physical = psutil.cpu_count(logical=False)
self.assertGreaterEqual(physical, 1)
self.assertGreaterEqual(logical, physical)
@@ -1127,13 +1136,30 @@ class TestProcess(unittest.TestCase):
def test_send_signal(self):
sig = signal.SIGKILL if POSIX else signal.SIGTERM
sproc = get_test_subprocess()
- test_pid = sproc.pid
- p = psutil.Process(test_pid)
+ p = psutil.Process(sproc.pid)
p.send_signal(sig)
exit_sig = p.wait()
- self.assertFalse(psutil.pid_exists(test_pid))
+ self.assertFalse(psutil.pid_exists(p.pid))
if POSIX:
self.assertEqual(exit_sig, sig)
+ #
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ p.send_signal(sig)
+ with mock.patch('psutil.os.kill',
+ side_effect=OSError(errno.ESRCH, "")) as fun:
+ with self.assertRaises(psutil.NoSuchProcess):
+ p.send_signal(sig)
+ assert fun.called
+ #
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ p.send_signal(sig)
+ with mock.patch('psutil.os.kill',
+ side_effect=OSError(errno.EPERM, "")) as fun:
+ with self.assertRaises(psutil.AccessDenied):
+ p.send_signal(sig)
+ assert fun.called
def test_wait(self):
# check exit code signal
@@ -1352,7 +1378,20 @@ class TestProcess(unittest.TestCase):
ioclass, value = p.ionice()
self.assertEqual(ioclass, 2)
self.assertEqual(value, 7)
+ #
self.assertRaises(ValueError, p.ionice, 2, 10)
+ self.assertRaises(ValueError, p.ionice, 2, -1)
+ self.assertRaises(ValueError, p.ionice, 4)
+ self.assertRaises(TypeError, p.ionice, 2, "foo")
+ self.assertRaisesRegexp(
+ ValueError, "can't specify value with IOPRIO_CLASS_NONE",
+ p.ionice, psutil.IOPRIO_CLASS_NONE, 1)
+ self.assertRaisesRegexp(
+ ValueError, "can't specify value with IOPRIO_CLASS_IDLE",
+ p.ionice, psutil.IOPRIO_CLASS_IDLE, 1)
+ self.assertRaisesRegexp(
+ ValueError, "'ioclass' argument must be specified",
+ p.ionice, value=1)
finally:
p.ionice(IOPRIO_CLASS_NONE)
else:
@@ -1397,6 +1436,12 @@ class TestProcess(unittest.TestCase):
p = psutil.Process(sproc.pid)
p.rlimit(psutil.RLIMIT_NOFILE, (5, 5))
self.assertEqual(p.rlimit(psutil.RLIMIT_NOFILE), (5, 5))
+ # If pid is 0 prlimit() applies to the calling process and
+ # we don't want that.
+ with self.assertRaises(ValueError):
+ psutil._psplatform.Process(0).rlimit(0)
+ with self.assertRaises(ValueError):
+ p.rlimit(psutil.RLIMIT_NOFILE, (5, 5, 5))
def test_num_threads(self):
# on certain platforms such as Linux we might test for exact
@@ -1542,6 +1587,30 @@ class TestProcess(unittest.TestCase):
pyexe = os.path.basename(os.path.realpath(sys.executable)).lower()
assert pyexe.startswith(name), (pyexe, name)
+ @unittest.skipUnless(POSIX, "posix only")
+ # TODO: add support for other compilers
+ @unittest.skipUnless(which("gcc"), "gcc not available")
+ def test_prog_w_funky_name(self):
+ # Test that name(), exe() and cmdline() correctly handle programs
+ # with funky chars such as spaces and ")", see:
+ # https://github.com/giampaolo/psutil/issues/628
+ funky_name = "/tmp/foo bar )"
+ _, c_file = tempfile.mkstemp(prefix='psutil-', suffix='.c', dir="/tmp")
+ self.addCleanup(lambda: safe_remove(c_file))
+ self.addCleanup(lambda: safe_remove(funky_name))
+ with open(c_file, "w") as f:
+ f.write("void main() { pause(); }")
+ subprocess.check_call(["gcc", c_file, "-o", funky_name])
+ sproc = get_test_subprocess(
+ [funky_name, "arg1", "arg2", "", "arg3", ""])
+ p = psutil.Process(sproc.pid)
+ # ...in order to try to prevent occasional failures on travis
+ wait_for_pid(p.pid)
+ self.assertEqual(p.name(), "foo bar )")
+ self.assertEqual(p.exe(), "/tmp/foo bar )")
+ self.assertEqual(
+ p.cmdline(), ["/tmp/foo bar )", "arg1", "arg2", "", "arg3", ""])
+
@unittest.skipUnless(POSIX, 'posix only')
def test_uids(self):
p = psutil.Process()
@@ -1613,6 +1682,11 @@ class TestProcess(unittest.TestCase):
if POSIX:
import pwd
self.assertEqual(p.username(), pwd.getpwuid(os.getuid()).pw_name)
+ with mock.patch("psutil.pwd.getpwuid",
+ side_effect=KeyError) as fun:
+ p.username() == str(p.uids().real)
+ assert fun.called
+
elif WINDOWS and 'USERNAME' in os.environ:
expected_username = os.environ['USERNAME']
expected_domain = os.environ['USERDOMAIN']
@@ -1677,6 +1751,7 @@ class TestProcess(unittest.TestCase):
files = p.open_files()
self.assertFalse(TESTFN in files)
with open(TESTFN, 'w'):
+ # give the kernel some time to see the new file
call_until(p.open_files, "len(ret) != %i" % len(files))
filenames = [x.path for x in p.open_files()]
self.assertIn(TESTFN, filenames)
@@ -2158,6 +2233,10 @@ class TestProcess(unittest.TestCase):
except psutil.AccessDenied:
pass
+ self.assertRaisesRegexp(
+ ValueError, "preventing sending signal to process with PID 0",
+ p.send_signal(signal.SIGTERM))
+
self.assertIn(p.ppid(), (0, 1))
# self.assertEqual(p.exe(), "")
p.cmdline()
@@ -2171,7 +2250,6 @@ class TestProcess(unittest.TestCase):
except psutil.AccessDenied:
pass
- # username property
try:
if POSIX:
self.assertEqual(p.username(), 'root')
@@ -2198,6 +2276,7 @@ class TestProcess(unittest.TestCase):
proc.stdin
self.assertTrue(hasattr(proc, 'name'))
self.assertTrue(hasattr(proc, 'stdin'))
+ self.assertTrue(dir(proc))
self.assertRaises(AttributeError, getattr, proc, 'foo')
finally:
proc.kill()
@@ -2545,6 +2624,19 @@ class TestMisc(unittest.TestCase):
p.wait()
self.assertIn(str(sproc.pid), str(p))
self.assertIn("terminated", str(p))
+ # test error conditions
+ with mock.patch.object(psutil.Process, 'name',
+ side_effect=psutil.ZombieProcess(1)) as meth:
+ self.assertIn("zombie", str(p))
+ self.assertIn("pid", str(p))
+ assert meth.called
+ with mock.patch.object(psutil.Process, 'name',
+ side_effect=psutil.AccessDenied) as meth:
+ self.assertIn("pid", str(p))
+ assert meth.called
+
+ def test__repr__(self):
+ repr(psutil.Process())
def test__eq__(self):
p1 = psutil.Process()
@@ -2635,6 +2727,29 @@ class TestMisc(unittest.TestCase):
check(psutil.disk_usage(os.getcwd()))
check(psutil.users())
+ def test_setup_script(self):
+ here = os.path.abspath(os.path.dirname(__file__))
+ setup_py = os.path.realpath(os.path.join(here, '..', 'setup.py'))
+ module = imp.load_source('setup', setup_py)
+ self.assertRaises(SystemExit, module.setup)
+
+ def test_ad_on_process_creation(self):
+ # We are supposed to be able to instantiate Process also in case
+ # of zombie processes or access denied.
+ with mock.patch.object(psutil.Process, 'create_time',
+ side_effect=psutil.AccessDenied) as meth:
+ psutil.Process()
+ assert meth.called
+ with mock.patch.object(psutil.Process, 'create_time',
+ side_effect=psutil.ZombieProcess(1)) as meth:
+ psutil.Process()
+ assert meth.called
+ with mock.patch.object(psutil.Process, 'create_time',
+ side_effect=ValueError) as meth:
+ with self.assertRaises(ValueError):
+ psutil.Process()
+ assert meth.called
+
# ===================================================================
# --- Example script tests
diff --git a/tox.ini b/tox.ini
index 25cee381..cf60729f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,9 +12,12 @@ deps =
flake8
pytest
py26: ipaddress
+ py26: mock
py26: unittest2
py27: ipaddress
+ py27: mock
py32: ipaddress
+ py32: mock
py33: ipaddress
setenv =