diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2015-07-09 11:43:48 +0200 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2015-07-09 11:43:48 +0200 |
commit | 192aa78c90253bede27b2745d4e80aeed79a3aef (patch) | |
tree | 3288344a8695ac4fd53162546fb051229c921e4d | |
parent | a6cb7f927f40bd129f31493c37dfe7d0d6f6a070 (diff) | |
parent | 73f54f47a7d4eac329c27af71c7fc5ad432d2e84 (diff) | |
download | psutil-192aa78c90253bede27b2745d4e80aeed79a3aef.tar.gz |
Merge branch 'master' into get_open_files_threadget_open_files_thread
-rwxr-xr-x[-rw-r--r--] | .git-pre-commit | 72 | ||||
-rw-r--r-- | .travis.yml | 13 | ||||
-rw-r--r-- | CREDITS | 22 | ||||
-rw-r--r-- | HISTORY.rst | 34 | ||||
-rw-r--r-- | INSTALL.rst | 2 | ||||
-rw-r--r-- | MANIFEST.in | 3 | ||||
-rw-r--r-- | Makefile | 50 | ||||
-rw-r--r-- | README.rst | 4 | ||||
-rw-r--r-- | docs/index.rst | 22 | ||||
-rw-r--r-- | make.bat | 160 | ||||
-rw-r--r-- | psutil/__init__.py | 57 | ||||
-rw-r--r-- | psutil/_common.py | 12 | ||||
-rw-r--r-- | psutil/_psbsd.py | 6 | ||||
-rw-r--r-- | psutil/_pslinux.py | 128 | ||||
-rw-r--r-- | psutil/_pssunos.py | 4 | ||||
-rw-r--r-- | psutil/_psutil_sunos.c | 8 | ||||
-rw-r--r-- | psutil/_psutil_windows.c | 17 | ||||
-rw-r--r-- | psutil/arch/windows/process_info.c | 5 | ||||
-rw-r--r-- | test/README | 15 | ||||
-rw-r--r-- | test/README.rst | 21 | ||||
-rw-r--r-- | test/_bsd.py | 11 | ||||
-rw-r--r-- | test/_linux.py | 162 | ||||
-rw-r--r-- | test/_osx.py | 9 | ||||
-rw-r--r-- | test/_posix.py | 7 | ||||
-rw-r--r-- | test/_sunos.py | 10 | ||||
-rw-r--r-- | test/_windows.py | 18 | ||||
-rw-r--r-- | test/test_memory_leaks.py | 39 | ||||
-rw-r--r-- | test/test_psutil.py | 131 | ||||
-rw-r--r-- | tox.ini | 3 |
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 @@ -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* @@ -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 @@ -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 @@ -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 @@ -12,9 +12,12 @@ deps = flake8 pytest py26: ipaddress + py26: mock py26: unittest2 py27: ipaddress + py27: mock py32: ipaddress + py32: mock py33: ipaddress setenv = |