summaryrefslogtreecommitdiff
path: root/psutil/tests
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2018-03-11 09:31:35 -0700
committerGitHub <noreply@github.com>2018-03-11 09:31:35 -0700
commit2ffb0ecc5e41c5486056fcd00aca274d77858860 (patch)
tree5f0325b14d059f76d566dfe2e2ec814dc5b42e21 /psutil/tests
parentb578d2febfd35f80fcfa1ce1bdf18d44d21b1581 (diff)
downloadpsutil-2ffb0ecc5e41c5486056fcd00aca274d77858860.tar.gz
Revert "Move tests out of package to top level directory (#1232)" (#1242)
This reverts commit b578d2febfd35f80fcfa1ce1bdf18d44d21b1581.
Diffstat (limited to 'psutil/tests')
-rw-r--r--psutil/tests/README.rst23
-rw-r--r--psutil/tests/__init__.py1198
-rwxr-xr-xpsutil/tests/__main__.py94
-rwxr-xr-xpsutil/tests/test_aix.py121
-rwxr-xr-xpsutil/tests/test_bsd.py489
-rwxr-xr-xpsutil/tests/test_connections.py525
-rwxr-xr-xpsutil/tests/test_contracts.py650
-rwxr-xr-xpsutil/tests/test_linux.py1910
-rwxr-xr-xpsutil/tests/test_memory_leaks.py599
-rwxr-xr-xpsutil/tests/test_misc.py1046
-rwxr-xr-xpsutil/tests/test_osx.py303
-rwxr-xr-xpsutil/tests/test_posix.py418
-rwxr-xr-xpsutil/tests/test_process.py1567
-rwxr-xr-xpsutil/tests/test_sunos.py45
-rwxr-xr-xpsutil/tests/test_system.py862
-rwxr-xr-xpsutil/tests/test_unicode.py366
-rwxr-xr-xpsutil/tests/test_windows.py837
17 files changed, 11053 insertions, 0 deletions
diff --git a/psutil/tests/README.rst b/psutil/tests/README.rst
new file mode 100644
index 00000000..515abf77
--- /dev/null
+++ b/psutil/tests/README.rst
@@ -0,0 +1,23 @@
+Instructions for running tests
+==============================
+
+* There are two ways of running tests. As a "user", if psutil is already
+ installed and you just want to test it works::
+
+ python -m psutil.tests --install-deps # install test deps
+ python -m psutil.tests
+
+ As a "developer", if you have a copy of the source code and you whish to hack
+ on psutil::
+
+ make setup-dev-env # install test deps (+ other things)
+ make test
+
+* To run tests on all supported Python versions install tox
+ (``pip install tox``) then run ``tox`` from within psutil root directory.
+
+* Every time a commit is pushed tests are automatically run on Travis
+ (Linux, OSX) and appveyor (Windows):
+
+ * Travis builds: https://travis-ci.org/giampaolo/psutil
+ * AppVeyor builds: https://ci.appveyor.com/project/giampaolo/psutil
diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py
new file mode 100644
index 00000000..499240fb
--- /dev/null
+++ b/psutil/tests/__init__.py
@@ -0,0 +1,1198 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Test utilities.
+"""
+
+from __future__ import print_function
+
+import atexit
+import contextlib
+import ctypes
+import errno
+import functools
+import os
+import random
+import re
+import select
+import shutil
+import socket
+import stat
+import subprocess
+import sys
+import tempfile
+import textwrap
+import threading
+import time
+import traceback
+import warnings
+from socket import AF_INET
+from socket import AF_INET6
+from socket import SOCK_DGRAM
+from socket import SOCK_STREAM
+
+import psutil
+from psutil import OSX
+from psutil import POSIX
+from psutil import SUNOS
+from psutil import WINDOWS
+from psutil._common import supports_ipv6
+from psutil._compat import PY3
+from psutil._compat import u
+from psutil._compat import unicode
+from psutil._compat import which
+
+if sys.version_info < (2, 7):
+ import unittest2 as unittest # requires "pip install unittest2"
+else:
+ import unittest
+
+try:
+ from unittest import mock # py3
+except ImportError:
+ import mock # NOQA - requires "pip install mock"
+
+if sys.version_info >= (3, 4):
+ import enum
+else:
+ enum = None
+
+
+__all__ = [
+ # constants
+ 'APPVEYOR', 'DEVNULL', 'GLOBAL_TIMEOUT', 'MEMORY_TOLERANCE', 'NO_RETRIES',
+ 'PYPY', 'PYTHON_EXE', 'ROOT_DIR', 'SCRIPTS_DIR', 'TESTFILE_PREFIX',
+ 'TESTFN', 'TESTFN_UNICODE', 'TOX', 'TRAVIS', 'VALID_PROC_STATUSES',
+ 'VERBOSITY',
+ "HAS_CPU_AFFINITY", "HAS_CPU_FREQ", "HAS_ENVIRON", "HAS_PROC_IO_COUNTERS",
+ "HAS_IONICE", "HAS_MEMORY_MAPS", "HAS_PROC_CPU_NUM", "HAS_RLIMIT",
+ "HAS_SENSORS_BATTERY", "HAS_BATTERY", "HAS_SENSORS_FANS",
+ "HAS_SENSORS_TEMPERATURES", "HAS_MEMORY_FULL_INFO",
+ # subprocesses
+ 'pyrun', 'reap_children', 'get_test_subprocess', 'create_zombie_proc',
+ 'create_proc_children_pair',
+ # threads
+ 'ThreadTask'
+ # test utils
+ 'unittest', 'skip_on_access_denied', 'skip_on_not_implemented',
+ 'retry_before_failing', 'run_test_module_by_name', 'get_suite',
+ 'run_suite',
+ # install utils
+ 'install_pip', 'install_test_deps',
+ # fs utils
+ 'chdir', 'safe_rmpath', 'create_exe', 'decode_path', 'encode_path',
+ 'unique_filename',
+ # os
+ 'get_winver', 'get_kernel_version',
+ # sync primitives
+ 'call_until', 'wait_for_pid', 'wait_for_file',
+ # network
+ 'check_connection_ntuple', 'check_net_address',
+ 'get_free_port', 'unix_socket_path', 'bind_socket', 'bind_unix_socket',
+ 'tcp_socketpair', 'unix_socketpair', 'create_sockets',
+ # compat
+ 'reload_module', 'import_module_by_path',
+ # others
+ 'warn', 'copyload_shared_lib', 'is_namedtuple',
+]
+
+
+# ===================================================================
+# --- constants
+# ===================================================================
+
+# --- platforms
+
+TOX = os.getenv('TOX') or '' in ('1', 'true')
+PYPY = '__pypy__' in sys.builtin_module_names
+WIN_VISTA = (6, 0, 0) if WINDOWS else None
+# whether we're running this test suite on Travis (https://travis-ci.org/)
+TRAVIS = bool(os.environ.get('TRAVIS'))
+# whether we're running this test suite on Appveyor for Windows
+# (http://www.appveyor.com/)
+APPVEYOR = bool(os.environ.get('APPVEYOR'))
+
+# --- configurable defaults
+
+# how many times retry_before_failing() decorator will retry
+NO_RETRIES = 10
+# bytes tolerance for system-wide memory related tests
+MEMORY_TOLERANCE = 500 * 1024 # 500KB
+# the timeout used in functions which have to wait
+GLOBAL_TIMEOUT = 3
+# test output verbosity
+VERBOSITY = 1 if os.getenv('SILENT') or TOX else 2
+# be more tolerant if we're on travis / appveyor in order to avoid
+# false positives
+if TRAVIS or APPVEYOR:
+ NO_RETRIES *= 3
+ GLOBAL_TIMEOUT *= 3
+
+# --- files
+
+TESTFILE_PREFIX = '$testfn'
+TESTFN = os.path.join(os.path.realpath(os.getcwd()), TESTFILE_PREFIX)
+_TESTFN = TESTFN + '-internal'
+TESTFN_UNICODE = TESTFN + u("-ƒőő")
+ASCII_FS = sys.getfilesystemencoding().lower() in ('ascii', 'us-ascii')
+
+# --- paths
+
+ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
+SCRIPTS_DIR = os.path.join(ROOT_DIR, 'scripts')
+HERE = os.path.abspath(os.path.dirname(__file__))
+
+# --- support
+
+HAS_CPU_AFFINITY = hasattr(psutil.Process, "cpu_affinity")
+HAS_CPU_FREQ = hasattr(psutil, "cpu_freq")
+HAS_CONNECTIONS_UNIX = POSIX and not SUNOS
+HAS_ENVIRON = hasattr(psutil.Process, "environ")
+HAS_PROC_IO_COUNTERS = hasattr(psutil.Process, "io_counters")
+HAS_IONICE = hasattr(psutil.Process, "ionice")
+HAS_MEMORY_FULL_INFO = 'uss' in psutil.Process().memory_full_info()._fields
+HAS_MEMORY_MAPS = hasattr(psutil.Process, "memory_maps")
+HAS_PROC_CPU_NUM = hasattr(psutil.Process, "cpu_num")
+HAS_RLIMIT = hasattr(psutil.Process, "rlimit")
+HAS_THREADS = hasattr(psutil.Process, "threads")
+HAS_SENSORS_BATTERY = hasattr(psutil, "sensors_battery")
+HAS_BATTERY = HAS_SENSORS_BATTERY and psutil.sensors_battery()
+HAS_SENSORS_FANS = hasattr(psutil, "sensors_fans")
+HAS_SENSORS_TEMPERATURES = hasattr(psutil, "sensors_temperatures")
+
+# --- misc
+
+
+def _get_py_exe():
+ def attempt(exe):
+ try:
+ subprocess.check_call(
+ [exe, "-V"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ except Exception:
+ return None
+ else:
+ return exe
+
+ if OSX:
+ exe = \
+ attempt(sys.executable) or \
+ attempt(os.path.realpath(sys.executable)) or \
+ attempt(which("python%s.%s" % sys.version_info[:2])) or \
+ attempt(psutil.Process().exe())
+ if not exe:
+ raise ValueError("can't find python exe real abspath")
+ return exe
+ else:
+ exe = os.path.realpath(sys.executable)
+ assert os.path.exists(exe), exe
+ return exe
+
+
+PYTHON_EXE = _get_py_exe()
+DEVNULL = open(os.devnull, 'r+')
+VALID_PROC_STATUSES = [getattr(psutil, x) for x in dir(psutil)
+ if x.startswith('STATUS_')]
+AF_UNIX = getattr(socket, "AF_UNIX", object())
+SOCK_SEQPACKET = getattr(socket, "SOCK_SEQPACKET", object())
+
+_subprocesses_started = set()
+_pids_started = set()
+_testfiles_created = set()
+
+
+@atexit.register
+def _cleanup_files():
+ DEVNULL.close()
+ for name in os.listdir(u('.')):
+ if isinstance(name, unicode):
+ prefix = u(TESTFILE_PREFIX)
+ else:
+ prefix = TESTFILE_PREFIX
+ if name.startswith(prefix):
+ try:
+ safe_rmpath(name)
+ except Exception:
+ traceback.print_exc()
+ for path in _testfiles_created:
+ try:
+ safe_rmpath(path)
+ except Exception:
+ traceback.print_exc()
+
+
+# this is executed first
+@atexit.register
+def _cleanup_procs():
+ reap_children(recursive=True)
+
+
+# ===================================================================
+# --- threads
+# ===================================================================
+
+
+class ThreadTask(threading.Thread):
+ """A thread task which does nothing expect staying alive."""
+
+ def __init__(self):
+ threading.Thread.__init__(self)
+ self._running = False
+ self._interval = 0.001
+ self._flag = threading.Event()
+
+ def __repr__(self):
+ name = self.__class__.__name__
+ return '<%s running=%s at %#x>' % (name, self._running, id(self))
+
+ def __enter__(self):
+ self.start()
+ return self
+
+ def __exit__(self, *args, **kwargs):
+ self.stop()
+
+ def start(self):
+ """Start thread and keep it running until an explicit
+ stop() request. Polls for shutdown every 'timeout' seconds.
+ """
+ if self._running:
+ raise ValueError("already started")
+ threading.Thread.start(self)
+ self._flag.wait()
+
+ def run(self):
+ self._running = True
+ self._flag.set()
+ while self._running:
+ time.sleep(self._interval)
+
+ def stop(self):
+ """Stop thread execution and and waits until it is stopped."""
+ if not self._running:
+ raise ValueError("already stopped")
+ self._running = False
+ self.join()
+
+
+# ===================================================================
+# --- subprocesses
+# ===================================================================
+
+
+def _reap_children_on_err(fun):
+ @functools.wraps(fun)
+ def wrapper(*args, **kwargs):
+ try:
+ return fun(*args, **kwargs)
+ except Exception:
+ reap_children()
+ raise
+ return wrapper
+
+
+@_reap_children_on_err
+def get_test_subprocess(cmd=None, **kwds):
+ """Creates a python subprocess which does nothing for 60 secs and
+ return it as subprocess.Popen instance.
+ If "cmd" is specified that is used instead of python.
+ By default stdin and stdout are redirected to /dev/null.
+ It also attemps to make sure the process is in a reasonably
+ initialized state.
+ The process is registered for cleanup on reap_children().
+ """
+ kwds.setdefault("stdin", DEVNULL)
+ kwds.setdefault("stdout", DEVNULL)
+ kwds.setdefault("cwd", os.getcwd())
+ kwds.setdefault("env", os.environ)
+ if WINDOWS:
+ # Prevents the subprocess to open error dialogs.
+ kwds.setdefault("creationflags", 0x8000000) # CREATE_NO_WINDOW
+ if cmd is None:
+ safe_rmpath(_TESTFN)
+ pyline = "from time import sleep;" \
+ "open(r'%s', 'w').close();" \
+ "sleep(60);" % _TESTFN
+ cmd = [PYTHON_EXE, "-c", pyline]
+ sproc = subprocess.Popen(cmd, **kwds)
+ _subprocesses_started.add(sproc)
+ wait_for_file(_TESTFN, delete=True, empty=True)
+ else:
+ sproc = subprocess.Popen(cmd, **kwds)
+ _subprocesses_started.add(sproc)
+ wait_for_pid(sproc.pid)
+ return sproc
+
+
+@_reap_children_on_err
+def create_proc_children_pair():
+ """Create a subprocess which creates another one as in:
+ A (us) -> B (child) -> C (grandchild).
+ Return a (child, grandchild) tuple.
+ The 2 processes are fully initialized and will live for 60 secs
+ and are registered for cleanup on reap_children().
+ """
+ _TESTFN2 = os.path.basename(_TESTFN) + '2' # need to be relative
+ s = textwrap.dedent("""\
+ import subprocess, os, sys, time
+ s = "import os, time;"
+ s += "f = open('%s', 'w');"
+ s += "f.write(str(os.getpid()));"
+ s += "f.close();"
+ s += "time.sleep(60);"
+ subprocess.Popen(['%s', '-c', s])
+ time.sleep(60)
+ """ % (_TESTFN2, PYTHON_EXE))
+ # On Windows if we create a subprocess with CREATE_NO_WINDOW flag
+ # set (which is the default) a "conhost.exe" extra process will be
+ # spawned as a child. We don't want that.
+ if WINDOWS:
+ subp = pyrun(s, creationflags=0)
+ else:
+ subp = pyrun(s)
+ child1 = psutil.Process(subp.pid)
+ data = wait_for_file(_TESTFN2, delete=False, empty=False)
+ os.remove(_TESTFN2)
+ child2_pid = int(data)
+ _pids_started.add(child2_pid)
+ child2 = psutil.Process(child2_pid)
+ return (child1, child2)
+
+
+def create_zombie_proc():
+ """Create a zombie process and return its PID."""
+ assert psutil.POSIX
+ unix_file = tempfile.mktemp(prefix=TESTFILE_PREFIX) if OSX else TESTFN
+ src = textwrap.dedent("""\
+ import os, sys, time, socket, contextlib
+ child_pid = os.fork()
+ if child_pid > 0:
+ time.sleep(3000)
+ else:
+ # this is the zombie process
+ s = socket.socket(socket.AF_UNIX)
+ with contextlib.closing(s):
+ s.connect('%s')
+ if sys.version_info < (3, ):
+ pid = str(os.getpid())
+ else:
+ pid = bytes(str(os.getpid()), 'ascii')
+ s.sendall(pid)
+ """ % unix_file)
+ with contextlib.closing(socket.socket(socket.AF_UNIX)) as sock:
+ sock.settimeout(GLOBAL_TIMEOUT)
+ sock.bind(unix_file)
+ sock.listen(1)
+ pyrun(src)
+ conn, _ = sock.accept()
+ try:
+ select.select([conn.fileno()], [], [], GLOBAL_TIMEOUT)
+ zpid = int(conn.recv(1024))
+ _pids_started.add(zpid)
+ zproc = psutil.Process(zpid)
+ call_until(lambda: zproc.status(), "ret == psutil.STATUS_ZOMBIE")
+ return zpid
+ finally:
+ conn.close()
+
+
+@_reap_children_on_err
+def pyrun(src, **kwds):
+ """Run python 'src' code string in a separate interpreter.
+ Returns a subprocess.Popen instance.
+ """
+ kwds.setdefault("stdout", None)
+ kwds.setdefault("stderr", None)
+ with tempfile.NamedTemporaryFile(
+ prefix=TESTFILE_PREFIX, mode="wt", delete=False) as f:
+ _testfiles_created.add(f.name)
+ f.write(src)
+ f.flush()
+ subp = get_test_subprocess([PYTHON_EXE, f.name], **kwds)
+ wait_for_pid(subp.pid)
+ return subp
+
+
+@_reap_children_on_err
+def sh(cmd, **kwds):
+ """run cmd in a subprocess and return its output.
+ raises RuntimeError on error.
+ """
+ shell = True if isinstance(cmd, (str, unicode)) else False
+ # Prevents subprocess to open error dialogs in case of error.
+ flags = 0x8000000 if WINDOWS and shell else 0
+ kwds.setdefault("shell", shell)
+ kwds.setdefault("stdout", subprocess.PIPE)
+ kwds.setdefault("stderr", subprocess.PIPE)
+ kwds.setdefault("universal_newlines", True)
+ kwds.setdefault("creationflags", flags)
+ p = subprocess.Popen(cmd, **kwds)
+ _subprocesses_started.add(p)
+ stdout, stderr = p.communicate()
+ if p.returncode != 0:
+ raise RuntimeError(stderr)
+ if stderr:
+ warn(stderr)
+ if stdout.endswith('\n'):
+ stdout = stdout[:-1]
+ return stdout
+
+
+def reap_children(recursive=False):
+ """Terminate and wait() any subprocess started by this test suite
+ and ensure that no zombies stick around to hog resources and
+ create problems when looking for refleaks.
+
+ If resursive is True it also tries to terminate and wait()
+ all grandchildren started by this process.
+ """
+ # This is here to make sure wait_procs() behaves properly and
+ # investigate:
+ # https://ci.appveyor.com/project/giampaolo/psutil/build/job/
+ # jiq2cgd6stsbtn60
+ def assert_gone(pid):
+ assert not psutil.pid_exists(pid), pid
+ assert pid not in psutil.pids(), pid
+ try:
+ p = psutil.Process(pid)
+ assert not p.is_running(), pid
+ except psutil.NoSuchProcess:
+ pass
+ else:
+ assert 0, "pid %s is not gone" % pid
+
+ # Get the children here, before terminating the children sub
+ # processes as we don't want to lose the intermediate reference
+ # in case of grandchildren.
+ if recursive:
+ children = set(psutil.Process().children(recursive=True))
+ else:
+ children = set()
+
+ # Terminate subprocess.Popen instances "cleanly" by closing their
+ # fds and wiat()ing for them in order to avoid zombies.
+ while _subprocesses_started:
+ subp = _subprocesses_started.pop()
+ _pids_started.add(subp.pid)
+ try:
+ subp.terminate()
+ except OSError as err:
+ if err.errno != errno.ESRCH:
+ raise
+ if subp.stdout:
+ subp.stdout.close()
+ if subp.stderr:
+ subp.stderr.close()
+ try:
+ # Flushing a BufferedWriter may raise an error.
+ if subp.stdin:
+ subp.stdin.close()
+ finally:
+ # Wait for the process to terminate, to avoid zombies.
+ try:
+ subp.wait()
+ except OSError as err:
+ if err.errno != errno.ECHILD:
+ raise
+
+ # Terminate started pids.
+ while _pids_started:
+ pid = _pids_started.pop()
+ try:
+ p = psutil.Process(pid)
+ except psutil.NoSuchProcess:
+ assert_gone(pid)
+ else:
+ children.add(p)
+
+ # Terminate children.
+ if children:
+ for p in children:
+ try:
+ p.terminate()
+ except psutil.NoSuchProcess:
+ pass
+ gone, alive = psutil.wait_procs(children, timeout=GLOBAL_TIMEOUT)
+ for p in alive:
+ warn("couldn't terminate process %r; attempting kill()" % p)
+ try:
+ p.kill()
+ except psutil.NoSuchProcess:
+ pass
+ gone, alive = psutil.wait_procs(alive, timeout=GLOBAL_TIMEOUT)
+ if alive:
+ for p in alive:
+ warn("process %r survived kill()" % p)
+
+ for p in children:
+ assert_gone(p.pid)
+
+
+# ===================================================================
+# --- OS
+# ===================================================================
+
+
+def get_kernel_version():
+ """Return a tuple such as (2, 6, 36)."""
+ if not POSIX:
+ raise NotImplementedError("not POSIX")
+ s = ""
+ uname = os.uname()[2]
+ for c in uname:
+ if c.isdigit() or c == '.':
+ s += c
+ else:
+ break
+ if not s:
+ raise ValueError("can't parse %r" % uname)
+ minor = 0
+ micro = 0
+ nums = s.split('.')
+ major = int(nums[0])
+ if len(nums) >= 2:
+ minor = int(nums[1])
+ if len(nums) >= 3:
+ micro = int(nums[2])
+ return (major, minor, micro)
+
+
+def get_winver():
+ if not WINDOWS:
+ raise NotImplementedError("not WINDOWS")
+ wv = sys.getwindowsversion()
+ if hasattr(wv, 'service_pack_major'): # python >= 2.7
+ sp = wv.service_pack_major or 0
+ else:
+ r = re.search(r"\s\d$", wv[4])
+ if r:
+ sp = int(r.group(0))
+ else:
+ sp = 0
+ return (wv[0], wv[1], sp)
+
+
+# ===================================================================
+# --- sync primitives
+# ===================================================================
+
+
+class retry(object):
+ """A retry decorator."""
+
+ def __init__(self,
+ exception=Exception,
+ timeout=None,
+ retries=None,
+ interval=0.001,
+ logfun=lambda s: print(s, file=sys.stderr),
+ ):
+ if timeout and retries:
+ raise ValueError("timeout and retries args are mutually exclusive")
+ self.exception = exception
+ self.timeout = timeout
+ self.retries = retries
+ self.interval = interval
+ self.logfun = logfun
+
+ def __iter__(self):
+ if self.timeout:
+ stop_at = time.time() + self.timeout
+ while time.time() < stop_at:
+ yield
+ elif self.retries:
+ for _ in range(self.retries):
+ yield
+ else:
+ while True:
+ yield
+
+ def sleep(self):
+ if self.interval is not None:
+ time.sleep(self.interval)
+
+ def __call__(self, fun):
+ @functools.wraps(fun)
+ def wrapper(*args, **kwargs):
+ exc = None
+ for _ in self:
+ try:
+ return fun(*args, **kwargs)
+ except self.exception as _:
+ exc = _
+ if self.logfun is not None:
+ self.logfun(exc)
+ self.sleep()
+ continue
+ if PY3:
+ raise exc
+ else:
+ raise
+
+ # This way the user of the decorated function can change config
+ # parameters.
+ wrapper.decorator = self
+ return wrapper
+
+
+@retry(exception=psutil.NoSuchProcess, logfun=None, timeout=GLOBAL_TIMEOUT,
+ interval=0.001)
+def wait_for_pid(pid):
+ """Wait for pid to show up in the process list then return.
+ Used in the test suite to give time the sub process to initialize.
+ """
+ psutil.Process(pid)
+ if WINDOWS:
+ # give it some more time to allow better initialization
+ time.sleep(0.01)
+
+
+@retry(exception=(EnvironmentError, AssertionError), logfun=None,
+ timeout=GLOBAL_TIMEOUT, interval=0.001)
+def wait_for_file(fname, delete=True, empty=False):
+ """Wait for a file to be written on disk with some content."""
+ with open(fname, "rb") as f:
+ data = f.read()
+ if not empty:
+ assert data
+ if delete:
+ os.remove(fname)
+ return data
+
+
+@retry(exception=AssertionError, logfun=None, timeout=GLOBAL_TIMEOUT,
+ interval=0.001)
+def call_until(fun, expr):
+ """Keep calling function for timeout secs and exit if eval()
+ expression is True.
+ """
+ ret = fun()
+ assert eval(expr)
+ return ret
+
+
+# ===================================================================
+# --- fs
+# ===================================================================
+
+
+def safe_rmpath(path):
+ "Convenience function for removing temporary test files or dirs"
+ try:
+ st = os.stat(path)
+ if stat.S_ISDIR(st.st_mode):
+ os.rmdir(path)
+ else:
+ os.remove(path)
+ except OSError as err:
+ if err.errno != errno.ENOENT:
+ raise
+
+
+def safe_mkdir(dir):
+ "Convenience function for creating a directory"
+ try:
+ os.mkdir(dir)
+ except OSError as err:
+ if err.errno != errno.EEXIST:
+ raise
+
+
+@contextlib.contextmanager
+def chdir(dirname):
+ "Context manager which temporarily changes the current directory."
+ curdir = os.getcwd()
+ try:
+ os.chdir(dirname)
+ yield
+ finally:
+ os.chdir(curdir)
+
+
+def create_exe(outpath, c_code=None):
+ """Creates an executable file in the given location."""
+ assert not os.path.exists(outpath), outpath
+ if c_code:
+ if not which("gcc"):
+ raise ValueError("gcc is not installed")
+ if isinstance(c_code, bool): # c_code is True
+ c_code = textwrap.dedent(
+ """
+ #include <unistd.h>
+ int main() {
+ pause();
+ return 1;
+ }
+ """)
+ assert isinstance(c_code, str), c_code
+ with tempfile.NamedTemporaryFile(
+ suffix='.c', delete=False, mode='wt') as f:
+ f.write(c_code)
+ try:
+ subprocess.check_call(["gcc", f.name, "-o", outpath])
+ finally:
+ safe_rmpath(f.name)
+ else:
+ # copy python executable
+ shutil.copyfile(PYTHON_EXE, outpath)
+ if POSIX:
+ st = os.stat(outpath)
+ os.chmod(outpath, st.st_mode | stat.S_IEXEC)
+
+
+def unique_filename(prefix=TESTFILE_PREFIX, suffix=""):
+ return tempfile.mktemp(prefix=prefix, suffix=suffix)
+
+
+# ===================================================================
+# --- testing
+# ===================================================================
+
+
+class TestCase(unittest.TestCase):
+
+ # Print a full path representation of the single unit tests
+ # being run.
+ def __str__(self):
+ return "%s.%s.%s" % (
+ self.__class__.__module__, self.__class__.__name__,
+ self._testMethodName)
+
+ # assertRaisesRegexp renamed to assertRaisesRegex in 3.3;
+ # add support for the new name.
+ if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
+ assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
+
+
+# override default unittest.TestCase
+unittest.TestCase = TestCase
+
+
+def _setup_tests():
+ if 'PSUTIL_TESTING' not in os.environ:
+ # This won't work on Windows but set_testing() below will do it.
+ os.environ['PSUTIL_TESTING'] = '1'
+ psutil._psplatform.cext.set_testing()
+
+
+def get_suite():
+ testmods = [os.path.splitext(x)[0] for x in os.listdir(HERE)
+ if x.endswith('.py') and x.startswith('test_') and not
+ x.startswith('test_memory_leaks')]
+ if "WHEELHOUSE_UPLOADER_USERNAME" in os.environ:
+ testmods = [x for x in testmods if not x.endswith((
+ "osx", "posix", "linux"))]
+ suite = unittest.TestSuite()
+ for tm in testmods:
+ # ...so that the full test paths are printed on screen
+ tm = "psutil.tests.%s" % tm
+ suite.addTest(unittest.defaultTestLoader.loadTestsFromName(tm))
+ return suite
+
+
+def run_suite():
+ _setup_tests()
+ result = unittest.TextTestRunner(verbosity=VERBOSITY).run(get_suite())
+ success = result.wasSuccessful()
+ sys.exit(0 if success else 1)
+
+
+def run_test_module_by_name(name):
+ # testmodules = [os.path.splitext(x)[0] for x in os.listdir(HERE)
+ # if x.endswith('.py') and x.startswith('test_')]
+ _setup_tests()
+ name = os.path.splitext(os.path.basename(name))[0]
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.defaultTestLoader.loadTestsFromName(name))
+ result = unittest.TextTestRunner(verbosity=VERBOSITY).run(suite)
+ success = result.wasSuccessful()
+ sys.exit(0 if success else 1)
+
+
+def retry_before_failing(retries=NO_RETRIES):
+ """Decorator which runs a test function and retries N times before
+ actually failing.
+ """
+ return retry(exception=AssertionError, timeout=None, retries=retries)
+
+
+def skip_on_access_denied(only_if=None):
+ """Decorator to Ignore AccessDenied exceptions."""
+ def decorator(fun):
+ @functools.wraps(fun)
+ def wrapper(*args, **kwargs):
+ try:
+ return fun(*args, **kwargs)
+ except psutil.AccessDenied:
+ if only_if is not None:
+ if not only_if:
+ raise
+ raise unittest.SkipTest("raises AccessDenied")
+ return wrapper
+ return decorator
+
+
+def skip_on_not_implemented(only_if=None):
+ """Decorator to Ignore NotImplementedError exceptions."""
+ def decorator(fun):
+ @functools.wraps(fun)
+ def wrapper(*args, **kwargs):
+ try:
+ return fun(*args, **kwargs)
+ except NotImplementedError:
+ if only_if is not None:
+ if not only_if:
+ raise
+ msg = "%r was skipped because it raised NotImplementedError" \
+ % fun.__name__
+ raise unittest.SkipTest(msg)
+ return wrapper
+ return decorator
+
+
+# ===================================================================
+# --- network
+# ===================================================================
+
+
+def get_free_port(host='127.0.0.1'):
+ """Return an unused TCP port."""
+ with contextlib.closing(socket.socket()) as sock:
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind((host, 0))
+ return sock.getsockname()[1]
+
+
+@contextlib.contextmanager
+def unix_socket_path(suffix=""):
+ """A context manager which returns a non-existent file name
+ and tries to delete it on exit.
+ """
+ assert psutil.POSIX
+ path = unique_filename(suffix=suffix)
+ try:
+ yield path
+ finally:
+ try:
+ os.unlink(path)
+ except OSError:
+ pass
+
+
+def bind_socket(family=AF_INET, type=SOCK_STREAM, addr=None):
+ """Binds a generic socket."""
+ if addr is None and family in (AF_INET, AF_INET6):
+ addr = ("", 0)
+ sock = socket.socket(family, type)
+ try:
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind(addr)
+ if type == socket.SOCK_STREAM:
+ sock.listen(10)
+ return sock
+ except Exception:
+ sock.close()
+ raise
+
+
+def bind_unix_socket(name, type=socket.SOCK_STREAM):
+ """Bind a UNIX socket."""
+ assert psutil.POSIX
+ assert not os.path.exists(name), name
+ sock = socket.socket(socket.AF_UNIX, type)
+ try:
+ sock.bind(name)
+ if type == socket.SOCK_STREAM:
+ sock.listen(10)
+ except Exception:
+ sock.close()
+ raise
+ return sock
+
+
+def tcp_socketpair(family, addr=("", 0)):
+ """Build a pair of TCP sockets connected to each other.
+ Return a (server, client) tuple.
+ """
+ with contextlib.closing(socket.socket(family, SOCK_STREAM)) as ll:
+ ll.bind(addr)
+ ll.listen(10)
+ addr = ll.getsockname()
+ c = socket.socket(family, SOCK_STREAM)
+ try:
+ c.connect(addr)
+ caddr = c.getsockname()
+ while True:
+ a, addr = ll.accept()
+ # check that we've got the correct client
+ if addr == caddr:
+ return (a, c)
+ a.close()
+ except OSError:
+ c.close()
+ raise
+
+
+def unix_socketpair(name):
+ """Build a pair of UNIX sockets connected to each other through
+ the same UNIX file name.
+ Return a (server, client) tuple.
+ """
+ assert psutil.POSIX
+ server = client = None
+ try:
+ server = bind_unix_socket(name, type=socket.SOCK_STREAM)
+ server.setblocking(0)
+ client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ client.setblocking(0)
+ client.connect(name)
+ # new = server.accept()
+ except Exception:
+ if server is not None:
+ server.close()
+ if client is not None:
+ client.close()
+ raise
+ return (server, client)
+
+
+@contextlib.contextmanager
+def create_sockets():
+ """Open as many socket families / types as possible."""
+ socks = []
+ fname1 = fname2 = None
+ try:
+ socks.append(bind_socket(socket.AF_INET, socket.SOCK_STREAM))
+ socks.append(bind_socket(socket.AF_INET, socket.SOCK_DGRAM))
+ if supports_ipv6():
+ socks.append(bind_socket(socket.AF_INET6, socket.SOCK_STREAM))
+ socks.append(bind_socket(socket.AF_INET6, socket.SOCK_DGRAM))
+ if POSIX and HAS_CONNECTIONS_UNIX:
+ fname1 = unix_socket_path().__enter__()
+ fname2 = unix_socket_path().__enter__()
+ s1, s2 = unix_socketpair(fname1)
+ s3 = bind_unix_socket(fname2, type=socket.SOCK_DGRAM)
+ # self.addCleanup(safe_rmpath, fname1)
+ # self.addCleanup(safe_rmpath, fname2)
+ for s in (s1, s2, s3):
+ socks.append(s)
+ yield socks
+ finally:
+ for s in socks:
+ s.close()
+ if fname1 is not None:
+ safe_rmpath(fname1)
+ if fname2 is not None:
+ safe_rmpath(fname2)
+
+
+def check_net_address(addr, family):
+ """Check a net address validity. Supported families are IPv4,
+ IPv6 and MAC addresses.
+ """
+ import ipaddress # python >= 3.3 / requires "pip install ipaddress"
+ if enum and PY3:
+ assert isinstance(family, enum.IntEnum), family
+ if family == socket.AF_INET:
+ octs = [int(x) for x in addr.split('.')]
+ assert len(octs) == 4, addr
+ for num in octs:
+ assert 0 <= num <= 255, addr
+ if not PY3:
+ addr = unicode(addr)
+ ipaddress.IPv4Address(addr)
+ elif family == socket.AF_INET6:
+ assert isinstance(addr, str), addr
+ if not PY3:
+ addr = unicode(addr)
+ ipaddress.IPv6Address(addr)
+ elif family == psutil.AF_LINK:
+ assert re.match(r'([a-fA-F0-9]{2}[:|\-]?){6}', addr) is not None, addr
+ else:
+ raise ValueError("unknown family %r", family)
+
+
+def check_connection_ntuple(conn):
+ """Check validity of a connection namedtuple."""
+ # check ntuple
+ assert len(conn) in (6, 7), conn
+ has_pid = len(conn) == 7
+ has_fd = getattr(conn, 'fd', -1) != -1
+ assert conn[0] == conn.fd
+ assert conn[1] == conn.family
+ assert conn[2] == conn.type
+ assert conn[3] == conn.laddr
+ assert conn[4] == conn.raddr
+ assert conn[5] == conn.status
+ if has_pid:
+ assert conn[6] == conn.pid
+
+ # check fd
+ if has_fd:
+ assert conn.fd >= 0, conn
+ if hasattr(socket, 'fromfd') and not WINDOWS:
+ try:
+ dupsock = socket.fromfd(conn.fd, conn.family, conn.type)
+ except (socket.error, OSError) as err:
+ if err.args[0] != errno.EBADF:
+ raise
+ else:
+ with contextlib.closing(dupsock):
+ assert dupsock.family == conn.family
+ assert dupsock.type == conn.type
+
+ # check family
+ assert conn.family in (AF_INET, AF_INET6, AF_UNIX), repr(conn.family)
+ if conn.family in (AF_INET, AF_INET6):
+ # actually try to bind the local socket; ignore IPv6
+ # sockets as their address might be represented as
+ # an IPv4-mapped-address (e.g. "::127.0.0.1")
+ # and that's rejected by bind()
+ if conn.family == AF_INET:
+ s = socket.socket(conn.family, conn.type)
+ with contextlib.closing(s):
+ try:
+ s.bind((conn.laddr[0], 0))
+ except socket.error as err:
+ if err.errno != errno.EADDRNOTAVAIL:
+ raise
+ elif conn.family == AF_UNIX:
+ assert conn.status == psutil.CONN_NONE, conn.status
+
+ # check type (SOCK_SEQPACKET may happen in case of AF_UNIX socks)
+ assert conn.type in (SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET), \
+ repr(conn.type)
+ if conn.type == SOCK_DGRAM:
+ assert conn.status == psutil.CONN_NONE, conn.status
+
+ # check laddr (IP address and port sanity)
+ for addr in (conn.laddr, conn.raddr):
+ if conn.family in (AF_INET, AF_INET6):
+ assert isinstance(addr, tuple), addr
+ if not addr:
+ continue
+ assert isinstance(addr.port, int), addr.port
+ assert 0 <= addr.port <= 65535, addr.port
+ check_net_address(addr.ip, conn.family)
+ elif conn.family == AF_UNIX:
+ assert isinstance(addr, str), addr
+
+ # check status
+ assert isinstance(conn.status, str), conn
+ valids = [getattr(psutil, x) for x in dir(psutil) if x.startswith('CONN_')]
+ assert conn.status in valids, conn
+
+
+# ===================================================================
+# --- compatibility
+# ===================================================================
+
+
+def reload_module(module):
+ """Backport of importlib.reload of Python 3.3+."""
+ try:
+ import importlib
+ if not hasattr(importlib, 'reload'): # python <=3.3
+ raise ImportError
+ except ImportError:
+ import imp
+ return imp.reload(module)
+ else:
+ return importlib.reload(module)
+
+
+def import_module_by_path(path):
+ name = os.path.splitext(os.path.basename(path))[0]
+ if sys.version_info[0] == 2:
+ import imp
+ return imp.load_source(name, path)
+ elif sys.version_info[:2] <= (3, 4):
+ from importlib.machinery import SourceFileLoader
+ return SourceFileLoader(name, path).load_module()
+ else:
+ import importlib.util
+ spec = importlib.util.spec_from_file_location(name, path)
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod)
+ return mod
+
+
+# ===================================================================
+# --- others
+# ===================================================================
+
+
+def warn(msg):
+ """Raise a warning msg."""
+ warnings.warn(msg, UserWarning)
+
+
+def is_namedtuple(x):
+ """Check if object is an instance of namedtuple."""
+ t = type(x)
+ b = t.__bases__
+ if len(b) != 1 or b[0] != tuple:
+ return False
+ f = getattr(t, '_fields', None)
+ if not isinstance(f, tuple):
+ return False
+ return all(type(n) == str for n in f)
+
+
+if POSIX:
+ @contextlib.contextmanager
+ def copyload_shared_lib(dst_prefix=TESTFILE_PREFIX):
+ """Ctx manager which picks up a random shared CO lib used
+ by this process, copies it in another location and loads it
+ in memory via ctypes. Return the new absolutized path.
+ """
+ ext = ".so"
+ dst = tempfile.mktemp(prefix=dst_prefix, suffix=ext)
+ libs = [x.path for x in psutil.Process().memory_maps() if
+ os.path.splitext(x.path)[1] == ext and
+ 'python' in x.path.lower()]
+ src = random.choice(libs)
+ shutil.copyfile(src, dst)
+ try:
+ ctypes.CDLL(dst)
+ yield dst
+ finally:
+ safe_rmpath(dst)
+else:
+ @contextlib.contextmanager
+ def copyload_shared_lib(dst_prefix=TESTFILE_PREFIX):
+ """Ctx manager which picks up a random shared DLL lib used
+ by this process, copies it in another location and loads it
+ in memory via ctypes.
+ Return the new absolutized, normcased path.
+ """
+ from ctypes import wintypes
+ from ctypes import WinError
+ ext = ".dll"
+ dst = tempfile.mktemp(prefix=dst_prefix, suffix=ext)
+ libs = [x.path for x in psutil.Process().memory_maps() if
+ os.path.splitext(x.path)[1].lower() == ext and
+ 'python' in os.path.basename(x.path).lower() and
+ 'wow64' not in x.path.lower()]
+ src = random.choice(libs)
+ shutil.copyfile(src, dst)
+ cfile = None
+ try:
+ cfile = ctypes.WinDLL(dst)
+ yield dst
+ finally:
+ # Work around OverflowError:
+ # - https://ci.appveyor.com/project/giampaolo/psutil/build/1207/
+ # job/o53330pbnri9bcw7
+ # - http://bugs.python.org/issue30286
+ # - http://stackoverflow.com/questions/23522055
+ if cfile is not None:
+ FreeLibrary = ctypes.windll.kernel32.FreeLibrary
+ FreeLibrary.argtypes = [wintypes.HMODULE]
+ ret = FreeLibrary(cfile._handle)
+ if ret == 0:
+ WinError()
+ safe_rmpath(dst)
diff --git a/psutil/tests/__main__.py b/psutil/tests/__main__.py
new file mode 100755
index 00000000..62fe0742
--- /dev/null
+++ b/psutil/tests/__main__.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Run unit tests. This is invoked by:
+
+$ python -m psutil.tests
+"""
+
+import contextlib
+import optparse
+import os
+import ssl
+import sys
+import tempfile
+try:
+ from urllib.request import urlopen # py3
+except ImportError:
+ from urllib2 import urlopen
+
+from psutil.tests import PYTHON_EXE
+from psutil.tests import run_suite
+
+
+HERE = os.path.abspath(os.path.dirname(__file__))
+GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
+TEST_DEPS = []
+if sys.version_info[:2] == (2, 6):
+ TEST_DEPS.extend(["ipaddress", "unittest2", "argparse", "mock==1.0.1"])
+elif sys.version_info[:2] == (2, 7) or sys.version_info[:2] <= (3, 2):
+ TEST_DEPS.extend(["ipaddress", "mock"])
+
+
+def install_pip():
+ try:
+ import pip # NOQA
+ except ImportError:
+ f = tempfile.NamedTemporaryFile(suffix='.py')
+ with contextlib.closing(f):
+ print("downloading %s to %s" % (GET_PIP_URL, f.name))
+ if hasattr(ssl, '_create_unverified_context'):
+ ctx = ssl._create_unverified_context()
+ else:
+ ctx = None
+ kwargs = dict(context=ctx) if ctx else {}
+ req = urlopen(GET_PIP_URL, **kwargs)
+ data = req.read()
+ f.write(data)
+ f.flush()
+
+ print("installing pip")
+ code = os.system('%s %s --user' % (PYTHON_EXE, f.name))
+ return code
+
+
+def install_test_deps(deps=None):
+ """Install test dependencies via pip."""
+ if deps is None:
+ deps = TEST_DEPS
+ deps = set(deps)
+ if deps:
+ is_venv = hasattr(sys, 'real_prefix')
+ opts = "--user" if not is_venv else ""
+ install_pip()
+ code = os.system('%s -m pip install %s --upgrade %s' % (
+ PYTHON_EXE, opts, " ".join(deps)))
+ return code
+
+
+def main():
+ usage = "%s -m psutil.tests [opts]" % PYTHON_EXE
+ parser = optparse.OptionParser(usage=usage, description="run unit tests")
+ parser.add_option("-i", "--install-deps",
+ action="store_true", default=False,
+ help="don't print status messages to stdout")
+
+ opts, args = parser.parse_args()
+ if opts.install_deps:
+ install_pip()
+ install_test_deps()
+ else:
+ for dep in TEST_DEPS:
+ try:
+ __import__(dep.split("==")[0])
+ except ImportError:
+ sys.exit("%r lib is not installed; run %s -m psutil.tests "
+ "--install-deps" % (dep, PYTHON_EXE))
+ run_suite()
+
+
+main()
diff --git a/psutil/tests/test_aix.py b/psutil/tests/test_aix.py
new file mode 100755
index 00000000..7a8a4c33
--- /dev/null
+++ b/psutil/tests/test_aix.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'
+# Copyright (c) 2017, Arnon Yaari
+# All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""AIX specific tests."""
+
+import re
+
+from psutil import AIX
+from psutil.tests import run_test_module_by_name
+from psutil.tests import sh
+from psutil.tests import unittest
+import psutil
+
+
+@unittest.skipIf(not AIX, "AIX only")
+class AIXSpecificTestCase(unittest.TestCase):
+
+ def test_virtual_memory(self):
+ out = sh('/usr/bin/svmon -O unit=KB')
+ re_pattern = "memory\s*"
+ for field in ("size inuse free pin virtual available mmode").split():
+ re_pattern += "(?P<%s>\S+)\s+" % (field,)
+ matchobj = re.search(re_pattern, out)
+
+ self.assertIsNotNone(
+ matchobj, "svmon command returned unexpected output")
+
+ KB = 1024
+ total = int(matchobj.group("size")) * KB
+ available = int(matchobj.group("available")) * KB
+ used = int(matchobj.group("inuse")) * KB
+ free = int(matchobj.group("free")) * KB
+
+ psutil_result = psutil.virtual_memory()
+
+ # MEMORY_TOLERANCE from psutil.tests is not enough. For some reason
+ # we're seeing differences of ~1.2 MB. 2 MB is still a good tolerance
+ # when compared to GBs.
+ MEMORY_TOLERANCE = 2 * KB * KB # 2 MB
+ self.assertEqual(psutil_result.total, total)
+ self.assertAlmostEqual(
+ psutil_result.used, used, delta=MEMORY_TOLERANCE)
+ self.assertAlmostEqual(
+ psutil_result.available, available, delta=MEMORY_TOLERANCE)
+ self.assertAlmostEqual(
+ psutil_result.free, free, delta=MEMORY_TOLERANCE)
+
+ def test_swap_memory(self):
+ out = sh('/usr/sbin/lsps -a')
+ # From the man page, "The size is given in megabytes" so we assume
+ # we'll always have 'MB' in the result
+ # TODO maybe try to use "swap -l" to check "used" too, but its units
+ # are not guaranteed to be "MB" so parsing may not be consistent
+ matchobj = re.search("(?P<space>\S+)\s+"
+ "(?P<vol>\S+)\s+"
+ "(?P<vg>\S+)\s+"
+ "(?P<size>\d+)MB", out)
+
+ self.assertIsNotNone(
+ matchobj, "lsps command returned unexpected output")
+
+ total_mb = int(matchobj.group("size"))
+ MB = 1024 ** 2
+ psutil_result = psutil.swap_memory()
+ # we divide our result by MB instead of multiplying the lsps value by
+ # MB because lsps may round down, so we round down too
+ self.assertEqual(int(psutil_result.total / MB), total_mb)
+
+ def test_cpu_stats(self):
+ out = sh('/usr/bin/mpstat -a')
+
+ re_pattern = "ALL\s*"
+ for field in ("min maj mpcs mpcr dev soft dec ph cs ics bound rq "
+ "push S3pull S3grd S0rd S1rd S2rd S3rd S4rd S5rd "
+ "sysc").split():
+ re_pattern += "(?P<%s>\S+)\s+" % (field,)
+ matchobj = re.search(re_pattern, out)
+
+ self.assertIsNotNone(
+ matchobj, "mpstat command returned unexpected output")
+
+ # numbers are usually in the millions so 1000 is ok for tolerance
+ CPU_STATS_TOLERANCE = 1000
+ psutil_result = psutil.cpu_stats()
+ self.assertAlmostEqual(
+ psutil_result.ctx_switches,
+ int(matchobj.group("cs")),
+ delta=CPU_STATS_TOLERANCE)
+ self.assertAlmostEqual(
+ psutil_result.syscalls,
+ int(matchobj.group("sysc")),
+ delta=CPU_STATS_TOLERANCE)
+ self.assertAlmostEqual(
+ psutil_result.interrupts,
+ int(matchobj.group("dev")),
+ delta=CPU_STATS_TOLERANCE)
+ self.assertAlmostEqual(
+ psutil_result.soft_interrupts,
+ int(matchobj.group("soft")),
+ delta=CPU_STATS_TOLERANCE)
+
+ def test_cpu_count_logical(self):
+ out = sh('/usr/bin/mpstat -a')
+ mpstat_lcpu = int(re.search("lcpu=(\d+)", out).group(1))
+ psutil_lcpu = psutil.cpu_count(logical=True)
+ self.assertEqual(mpstat_lcpu, psutil_lcpu)
+
+ def test_net_if_addrs_names(self):
+ out = sh('/etc/ifconfig -l')
+ ifconfig_names = set(out.split())
+ psutil_names = set(psutil.net_if_addrs().keys())
+ self.assertSetEqual(ifconfig_names, psutil_names)
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_bsd.py b/psutil/tests/test_bsd.py
new file mode 100755
index 00000000..d3868ada
--- /dev/null
+++ b/psutil/tests/test_bsd.py
@@ -0,0 +1,489 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# TODO: (FreeBSD) add test for comparing connections with 'sockstat' cmd.
+
+
+"""Tests specific to all BSD platforms."""
+
+
+import datetime
+import os
+import re
+import time
+
+import psutil
+from psutil import BSD
+from psutil import FREEBSD
+from psutil import NETBSD
+from psutil import OPENBSD
+from psutil.tests import get_test_subprocess
+from psutil.tests import HAS_BATTERY
+from psutil.tests import MEMORY_TOLERANCE
+from psutil.tests import reap_children
+from psutil.tests import retry_before_failing
+from psutil.tests import run_test_module_by_name
+from psutil.tests import sh
+from psutil.tests import unittest
+from psutil.tests import which
+
+
+if BSD:
+ PAGESIZE = os.sysconf("SC_PAGE_SIZE")
+ if os.getuid() == 0: # muse requires root privileges
+ MUSE_AVAILABLE = which('muse')
+ else:
+ MUSE_AVAILABLE = False
+else:
+ MUSE_AVAILABLE = False
+
+
+def sysctl(cmdline):
+ """Expects a sysctl command with an argument and parse the result
+ returning only the value of interest.
+ """
+ result = sh("sysctl " + cmdline)
+ if FREEBSD:
+ result = result[result.find(": ") + 2:]
+ elif OPENBSD or NETBSD:
+ result = result[result.find("=") + 1:]
+ try:
+ return int(result)
+ except ValueError:
+ return result
+
+
+def muse(field):
+ """Thin wrapper around 'muse' cmdline utility."""
+ out = sh('muse')
+ for line in out.split('\n'):
+ if line.startswith(field):
+ break
+ else:
+ raise ValueError("line not found")
+ return int(line.split()[1])
+
+
+# =====================================================================
+# --- All BSD*
+# =====================================================================
+
+
+@unittest.skipIf(not BSD, "BSD only")
+class BSDSpecificTestCase(unittest.TestCase):
+ """Generic tests common to all BSD variants."""
+
+ @classmethod
+ def setUpClass(cls):
+ cls.pid = get_test_subprocess().pid
+
+ @classmethod
+ def tearDownClass(cls):
+ reap_children()
+
+ @unittest.skipIf(NETBSD, "-o lstart doesn't work on NETBSD")
+ def test_process_create_time(self):
+ output = sh("ps -o lstart -p %s" % self.pid)
+ start_ps = output.replace('STARTED', '').strip()
+ start_psutil = psutil.Process(self.pid).create_time()
+ start_psutil = time.strftime("%a %b %e %H:%M:%S %Y",
+ time.localtime(start_psutil))
+ self.assertEqual(start_ps, start_psutil)
+
+ def test_disks(self):
+ # test psutil.disk_usage() and psutil.disk_partitions()
+ # against "df -a"
+ def df(path):
+ out = sh('df -k "%s"' % path).strip()
+ lines = out.split('\n')
+ lines.pop(0)
+ line = lines.pop(0)
+ dev, total, used, free = line.split()[:4]
+ if dev == 'none':
+ dev = ''
+ total = int(total) * 1024
+ used = int(used) * 1024
+ free = int(free) * 1024
+ return dev, total, used, free
+
+ for part in psutil.disk_partitions(all=False):
+ usage = psutil.disk_usage(part.mountpoint)
+ dev, total, used, free = df(part.mountpoint)
+ self.assertEqual(part.device, dev)
+ self.assertEqual(usage.total, total)
+ # 10 MB tollerance
+ if abs(usage.free - free) > 10 * 1024 * 1024:
+ self.fail("psutil=%s, df=%s" % (usage.free, free))
+ if abs(usage.used - used) > 10 * 1024 * 1024:
+ self.fail("psutil=%s, df=%s" % (usage.used, used))
+
+ @unittest.skipIf(not which('sysctl'), "sysctl cmd not available")
+ def test_cpu_count_logical(self):
+ syst = sysctl("hw.ncpu")
+ self.assertEqual(psutil.cpu_count(logical=True), syst)
+
+ @unittest.skipIf(not which('sysctl'), "sysctl cmd not available")
+ def test_virtual_memory_total(self):
+ num = sysctl('hw.physmem')
+ self.assertEqual(num, psutil.virtual_memory().total)
+
+ def test_net_if_stats(self):
+ for name, stats in psutil.net_if_stats().items():
+ try:
+ out = sh("ifconfig %s" % name)
+ except RuntimeError:
+ pass
+ else:
+ self.assertEqual(stats.isup, 'RUNNING' in out, msg=out)
+ if "mtu" in out:
+ self.assertEqual(stats.mtu,
+ int(re.findall(r'mtu (\d+)', out)[0]))
+
+
+# =====================================================================
+# --- FreeBSD
+# =====================================================================
+
+
+@unittest.skipIf(not FREEBSD, "FREEBSD only")
+class FreeBSDSpecificTestCase(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.pid = get_test_subprocess().pid
+
+ @classmethod
+ def tearDownClass(cls):
+ reap_children()
+
+ @retry_before_failing()
+ def test_proc_memory_maps(self):
+ out = sh('procstat -v %s' % self.pid)
+ maps = psutil.Process(self.pid).memory_maps(grouped=False)
+ lines = out.split('\n')[1:]
+ while lines:
+ line = lines.pop()
+ fields = line.split()
+ _, start, stop, perms, res = fields[:5]
+ map = maps.pop()
+ self.assertEqual("%s-%s" % (start, stop), map.addr)
+ self.assertEqual(int(res), map.rss)
+ if not map.path.startswith('['):
+ self.assertEqual(fields[10], map.path)
+
+ def test_proc_exe(self):
+ out = sh('procstat -b %s' % self.pid)
+ self.assertEqual(psutil.Process(self.pid).exe(),
+ out.split('\n')[1].split()[-1])
+
+ def test_proc_cmdline(self):
+ out = sh('procstat -c %s' % self.pid)
+ self.assertEqual(' '.join(psutil.Process(self.pid).cmdline()),
+ ' '.join(out.split('\n')[1].split()[2:]))
+
+ def test_proc_uids_gids(self):
+ out = sh('procstat -s %s' % self.pid)
+ euid, ruid, suid, egid, rgid, sgid = out.split('\n')[1].split()[2:8]
+ p = psutil.Process(self.pid)
+ uids = p.uids()
+ gids = p.gids()
+ self.assertEqual(uids.real, int(ruid))
+ self.assertEqual(uids.effective, int(euid))
+ self.assertEqual(uids.saved, int(suid))
+ self.assertEqual(gids.real, int(rgid))
+ self.assertEqual(gids.effective, int(egid))
+ self.assertEqual(gids.saved, int(sgid))
+
+ @retry_before_failing()
+ def test_proc_ctx_switches(self):
+ tested = []
+ out = sh('procstat -r %s' % self.pid)
+ p = psutil.Process(self.pid)
+ for line in out.split('\n'):
+ line = line.lower().strip()
+ if ' voluntary context' in line:
+ pstat_value = int(line.split()[-1])
+ psutil_value = p.num_ctx_switches().voluntary
+ self.assertEqual(pstat_value, psutil_value)
+ tested.append(None)
+ elif ' involuntary context' in line:
+ pstat_value = int(line.split()[-1])
+ psutil_value = p.num_ctx_switches().involuntary
+ self.assertEqual(pstat_value, psutil_value)
+ tested.append(None)
+ if len(tested) != 2:
+ raise RuntimeError("couldn't find lines match in procstat out")
+
+ @retry_before_failing()
+ def test_proc_cpu_times(self):
+ tested = []
+ out = sh('procstat -r %s' % self.pid)
+ p = psutil.Process(self.pid)
+ for line in out.split('\n'):
+ line = line.lower().strip()
+ if 'user time' in line:
+ pstat_value = float('0.' + line.split()[-1].split('.')[-1])
+ psutil_value = p.cpu_times().user
+ self.assertEqual(pstat_value, psutil_value)
+ tested.append(None)
+ elif 'system time' in line:
+ pstat_value = float('0.' + line.split()[-1].split('.')[-1])
+ psutil_value = p.cpu_times().system
+ self.assertEqual(pstat_value, psutil_value)
+ tested.append(None)
+ if len(tested) != 2:
+ raise RuntimeError("couldn't find lines match in procstat out")
+
+ # --- virtual_memory(); tests against sysctl
+
+ @retry_before_failing()
+ def test_vmem_active(self):
+ syst = sysctl("vm.stats.vm.v_active_count") * PAGESIZE
+ self.assertAlmostEqual(psutil.virtual_memory().active, syst,
+ delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_vmem_inactive(self):
+ syst = sysctl("vm.stats.vm.v_inactive_count") * PAGESIZE
+ self.assertAlmostEqual(psutil.virtual_memory().inactive, syst,
+ delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_vmem_wired(self):
+ syst = sysctl("vm.stats.vm.v_wire_count") * PAGESIZE
+ self.assertAlmostEqual(psutil.virtual_memory().wired, syst,
+ delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_vmem_cached(self):
+ syst = sysctl("vm.stats.vm.v_cache_count") * PAGESIZE
+ self.assertAlmostEqual(psutil.virtual_memory().cached, syst,
+ delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_vmem_free(self):
+ syst = sysctl("vm.stats.vm.v_free_count") * PAGESIZE
+ self.assertAlmostEqual(psutil.virtual_memory().free, syst,
+ delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_vmem_buffers(self):
+ syst = sysctl("vfs.bufspace")
+ self.assertAlmostEqual(psutil.virtual_memory().buffers, syst,
+ delta=MEMORY_TOLERANCE)
+
+ # --- virtual_memory(); tests against muse
+
+ @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
+ def test_muse_vmem_total(self):
+ num = muse('Total')
+ self.assertEqual(psutil.virtual_memory().total, num)
+
+ @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
+ @retry_before_failing()
+ def test_muse_vmem_active(self):
+ num = muse('Active')
+ self.assertAlmostEqual(psutil.virtual_memory().active, num,
+ delta=MEMORY_TOLERANCE)
+
+ @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
+ @retry_before_failing()
+ def test_muse_vmem_inactive(self):
+ num = muse('Inactive')
+ self.assertAlmostEqual(psutil.virtual_memory().inactive, num,
+ delta=MEMORY_TOLERANCE)
+
+ @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
+ @retry_before_failing()
+ def test_muse_vmem_wired(self):
+ num = muse('Wired')
+ self.assertAlmostEqual(psutil.virtual_memory().wired, num,
+ delta=MEMORY_TOLERANCE)
+
+ @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
+ @retry_before_failing()
+ def test_muse_vmem_cached(self):
+ num = muse('Cache')
+ self.assertAlmostEqual(psutil.virtual_memory().cached, num,
+ delta=MEMORY_TOLERANCE)
+
+ @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
+ @retry_before_failing()
+ def test_muse_vmem_free(self):
+ num = muse('Free')
+ self.assertAlmostEqual(psutil.virtual_memory().free, num,
+ delta=MEMORY_TOLERANCE)
+
+ @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
+ @retry_before_failing()
+ def test_muse_vmem_buffers(self):
+ num = muse('Buffer')
+ self.assertAlmostEqual(psutil.virtual_memory().buffers, num,
+ delta=MEMORY_TOLERANCE)
+
+ def test_cpu_stats_ctx_switches(self):
+ self.assertAlmostEqual(psutil.cpu_stats().ctx_switches,
+ sysctl('vm.stats.sys.v_swtch'), delta=1000)
+
+ def test_cpu_stats_interrupts(self):
+ self.assertAlmostEqual(psutil.cpu_stats().interrupts,
+ sysctl('vm.stats.sys.v_intr'), delta=1000)
+
+ def test_cpu_stats_soft_interrupts(self):
+ self.assertAlmostEqual(psutil.cpu_stats().soft_interrupts,
+ sysctl('vm.stats.sys.v_soft'), delta=1000)
+
+ def test_cpu_stats_syscalls(self):
+ self.assertAlmostEqual(psutil.cpu_stats().syscalls,
+ sysctl('vm.stats.sys.v_syscall'), delta=1000)
+
+ # def test_cpu_stats_traps(self):
+ # self.assertAlmostEqual(psutil.cpu_stats().traps,
+ # sysctl('vm.stats.sys.v_trap'), delta=1000)
+
+ # --- others
+
+ def test_boot_time(self):
+ s = sysctl('sysctl kern.boottime')
+ s = s[s.find(" sec = ") + 7:]
+ s = s[:s.find(',')]
+ btime = int(s)
+ self.assertEqual(btime, psutil.boot_time())
+
+ # --- sensors_battery
+
+ @unittest.skipIf(not HAS_BATTERY, "no battery")
+ def test_sensors_battery(self):
+ def secs2hours(secs):
+ m, s = divmod(secs, 60)
+ h, m = divmod(m, 60)
+ return "%d:%02d" % (h, m)
+
+ out = sh("acpiconf -i 0")
+ fields = dict([(x.split('\t')[0], x.split('\t')[-1])
+ for x in out.split("\n")])
+ metrics = psutil.sensors_battery()
+ percent = int(fields['Remaining capacity:'].replace('%', ''))
+ remaining_time = fields['Remaining time:']
+ self.assertEqual(metrics.percent, percent)
+ if remaining_time == 'unknown':
+ self.assertEqual(metrics.secsleft, psutil.POWER_TIME_UNLIMITED)
+ else:
+ self.assertEqual(secs2hours(metrics.secsleft), remaining_time)
+
+ @unittest.skipIf(not HAS_BATTERY, "no battery")
+ def test_sensors_battery_against_sysctl(self):
+ self.assertEqual(psutil.sensors_battery().percent,
+ sysctl("hw.acpi.battery.life"))
+ self.assertEqual(psutil.sensors_battery().power_plugged,
+ sysctl("hw.acpi.acline") == 1)
+ secsleft = psutil.sensors_battery().secsleft
+ if secsleft < 0:
+ self.assertEqual(sysctl("hw.acpi.battery.time"), -1)
+ else:
+ self.assertEqual(secsleft, sysctl("hw.acpi.battery.time") * 60)
+
+ @unittest.skipIf(HAS_BATTERY, "has battery")
+ def test_sensors_battery_no_battery(self):
+ # If no battery is present one of these calls is supposed
+ # to fail, see:
+ # https://github.com/giampaolo/psutil/issues/1074
+ with self.assertRaises(RuntimeError):
+ sysctl("hw.acpi.battery.life")
+ sysctl("hw.acpi.battery.time")
+ sysctl("hw.acpi.acline")
+ self.assertIsNone(psutil.sensors_battery())
+
+
+# =====================================================================
+# --- OpenBSD
+# =====================================================================
+
+
+@unittest.skipIf(not OPENBSD, "OPENBSD only")
+class OpenBSDSpecificTestCase(unittest.TestCase):
+
+ def test_boot_time(self):
+ s = sysctl('kern.boottime')
+ sys_bt = datetime.datetime.strptime(s, "%a %b %d %H:%M:%S %Y")
+ psutil_bt = datetime.datetime.fromtimestamp(psutil.boot_time())
+ self.assertEqual(sys_bt, psutil_bt)
+
+
+# =====================================================================
+# --- NetBSD
+# =====================================================================
+
+
+@unittest.skipIf(not NETBSD, "NETBSD only")
+class NetBSDSpecificTestCase(unittest.TestCase):
+
+ @staticmethod
+ def parse_meminfo(look_for):
+ with open('/proc/meminfo', 'rb') as f:
+ for line in f:
+ if line.startswith(look_for):
+ return int(line.split()[1]) * 1024
+ raise ValueError("can't find %s" % look_for)
+
+ def test_vmem_total(self):
+ self.assertEqual(
+ psutil.virtual_memory().total, self.parse_meminfo("MemTotal:"))
+
+ def test_vmem_free(self):
+ self.assertAlmostEqual(
+ psutil.virtual_memory().free, self.parse_meminfo("MemFree:"),
+ delta=MEMORY_TOLERANCE)
+
+ def test_vmem_buffers(self):
+ self.assertAlmostEqual(
+ psutil.virtual_memory().buffers, self.parse_meminfo("Buffers:"),
+ delta=MEMORY_TOLERANCE)
+
+ def test_vmem_shared(self):
+ self.assertAlmostEqual(
+ psutil.virtual_memory().shared, self.parse_meminfo("MemShared:"),
+ delta=MEMORY_TOLERANCE)
+
+ def test_swapmem_total(self):
+ self.assertAlmostEqual(
+ psutil.swap_memory().total, self.parse_meminfo("SwapTotal:"),
+ delta=MEMORY_TOLERANCE)
+
+ def test_swapmem_free(self):
+ self.assertAlmostEqual(
+ psutil.swap_memory().free, self.parse_meminfo("SwapFree:"),
+ delta=MEMORY_TOLERANCE)
+
+ def test_swapmem_used(self):
+ smem = psutil.swap_memory()
+ self.assertEqual(smem.used, smem.total - smem.free)
+
+ def test_cpu_stats_interrupts(self):
+ with open('/proc/stat', 'rb') as f:
+ for line in f:
+ if line.startswith(b'intr'):
+ interrupts = int(line.split()[1])
+ break
+ else:
+ raise ValueError("couldn't find line")
+ self.assertAlmostEqual(
+ psutil.cpu_stats().interrupts, interrupts, delta=1000)
+
+ def test_cpu_stats_ctx_switches(self):
+ with open('/proc/stat', 'rb') as f:
+ for line in f:
+ if line.startswith(b'ctxt'):
+ ctx_switches = int(line.split()[1])
+ break
+ else:
+ raise ValueError("couldn't find line")
+ self.assertAlmostEqual(
+ psutil.cpu_stats().ctx_switches, ctx_switches, delta=1000)
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_connections.py b/psutil/tests/test_connections.py
new file mode 100755
index 00000000..176e2664
--- /dev/null
+++ b/psutil/tests/test_connections.py
@@ -0,0 +1,525 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Tests for net_connections() and Process.connections() APIs."""
+
+import os
+import socket
+import textwrap
+from contextlib import closing
+from socket import AF_INET
+from socket import AF_INET6
+from socket import SOCK_DGRAM
+from socket import SOCK_STREAM
+
+import psutil
+from psutil import FREEBSD
+from psutil import LINUX
+from psutil import NETBSD
+from psutil import OPENBSD
+from psutil import OSX
+from psutil import POSIX
+from psutil import SUNOS
+from psutil import WINDOWS
+from psutil._common import supports_ipv6
+from psutil._compat import PY3
+from psutil.tests import AF_UNIX
+from psutil.tests import bind_socket
+from psutil.tests import bind_unix_socket
+from psutil.tests import check_connection_ntuple
+from psutil.tests import create_sockets
+from psutil.tests import get_free_port
+from psutil.tests import HAS_CONNECTIONS_UNIX
+from psutil.tests import pyrun
+from psutil.tests import reap_children
+from psutil.tests import run_test_module_by_name
+from psutil.tests import safe_rmpath
+from psutil.tests import skip_on_access_denied
+from psutil.tests import tcp_socketpair
+from psutil.tests import TESTFN
+from psutil.tests import TRAVIS
+from psutil.tests import unittest
+from psutil.tests import unix_socket_path
+from psutil.tests import unix_socketpair
+from psutil.tests import wait_for_file
+
+
+thisproc = psutil.Process()
+
+
+class Base(object):
+
+ def setUp(self):
+ if not NETBSD:
+ # NetBSD opens a UNIX socket to /var/log/run.
+ cons = thisproc.connections(kind='all')
+ assert not cons, cons
+
+ def tearDown(self):
+ safe_rmpath(TESTFN)
+ reap_children()
+ if not NETBSD:
+ # Make sure we closed all resources.
+ # NetBSD opens a UNIX socket to /var/log/run.
+ cons = thisproc.connections(kind='all')
+ assert not cons, cons
+
+ def get_conn_from_sock(self, sock):
+ cons = thisproc.connections(kind='all')
+ smap = dict([(c.fd, c) for c in cons])
+ if NETBSD:
+ # NetBSD opens a UNIX socket to /var/log/run
+ # so there may be more connections.
+ return smap[sock.fileno()]
+ else:
+ self.assertEqual(len(cons), 1)
+ if cons[0].fd != -1:
+ self.assertEqual(smap[sock.fileno()].fd, sock.fileno())
+ return cons[0]
+
+ def check_socket(self, sock, conn=None):
+ """Given a socket, makes sure it matches the one obtained
+ via psutil. It assumes this process created one connection
+ only (the one supposed to be checked).
+ """
+ if conn is None:
+ conn = self.get_conn_from_sock(sock)
+ check_connection_ntuple(conn)
+
+ # fd, family, type
+ if conn.fd != -1:
+ self.assertEqual(conn.fd, sock.fileno())
+ self.assertEqual(conn.family, sock.family)
+ # see: http://bugs.python.org/issue30204
+ self.assertEqual(
+ conn.type, sock.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE))
+
+ # local address
+ laddr = sock.getsockname()
+ if not laddr and PY3 and isinstance(laddr, bytes):
+ # See: http://bugs.python.org/issue30205
+ laddr = laddr.decode()
+ if sock.family == AF_INET6:
+ laddr = laddr[:2]
+ if sock.family == AF_UNIX and OPENBSD:
+ # No addresses are set for UNIX sockets on OpenBSD.
+ pass
+ else:
+ self.assertEqual(conn.laddr, laddr)
+
+ # XXX Solaris can't retrieve system-wide UNIX sockets
+ if sock.family == AF_UNIX and HAS_CONNECTIONS_UNIX:
+ cons = thisproc.connections(kind='all')
+ self.compare_procsys_connections(os.getpid(), cons)
+ return conn
+
+ def compare_procsys_connections(self, pid, proc_cons, kind='all'):
+ """Given a process PID and its list of connections compare
+ those against system-wide connections retrieved via
+ psutil.net_connections.
+ """
+ try:
+ sys_cons = psutil.net_connections(kind=kind)
+ except psutil.AccessDenied:
+ # On OSX, system-wide connections are retrieved by iterating
+ # over all processes
+ if OSX:
+ return
+ else:
+ raise
+ # Filter for this proc PID and exlucde PIDs from the tuple.
+ sys_cons = [c[:-1] for c in sys_cons if c.pid == pid]
+ sys_cons.sort()
+ proc_cons.sort()
+ self.assertEqual(proc_cons, sys_cons)
+
+
+# =====================================================================
+# --- Test unconnected sockets
+# =====================================================================
+
+
+class TestUnconnectedSockets(Base, unittest.TestCase):
+ """Tests sockets which are open but not connected to anything."""
+
+ def test_tcp_v4(self):
+ addr = ("127.0.0.1", get_free_port())
+ with closing(bind_socket(AF_INET, SOCK_STREAM, addr=addr)) as sock:
+ conn = self.check_socket(sock)
+ assert not conn.raddr
+ self.assertEqual(conn.status, psutil.CONN_LISTEN)
+
+ @unittest.skipIf(not supports_ipv6(), "IPv6 not supported")
+ def test_tcp_v6(self):
+ addr = ("::1", get_free_port())
+ with closing(bind_socket(AF_INET6, SOCK_STREAM, addr=addr)) as sock:
+ conn = self.check_socket(sock)
+ assert not conn.raddr
+ self.assertEqual(conn.status, psutil.CONN_LISTEN)
+
+ def test_udp_v4(self):
+ addr = ("127.0.0.1", get_free_port())
+ with closing(bind_socket(AF_INET, SOCK_DGRAM, addr=addr)) as sock:
+ conn = self.check_socket(sock)
+ assert not conn.raddr
+ self.assertEqual(conn.status, psutil.CONN_NONE)
+
+ @unittest.skipIf(not supports_ipv6(), "IPv6 not supported")
+ def test_udp_v6(self):
+ addr = ("::1", get_free_port())
+ with closing(bind_socket(AF_INET6, SOCK_DGRAM, addr=addr)) as sock:
+ conn = self.check_socket(sock)
+ assert not conn.raddr
+ self.assertEqual(conn.status, psutil.CONN_NONE)
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ def test_unix_tcp(self):
+ with unix_socket_path() as name:
+ with closing(bind_unix_socket(name, type=SOCK_STREAM)) as sock:
+ conn = self.check_socket(sock)
+ assert not conn.raddr
+ self.assertEqual(conn.status, psutil.CONN_NONE)
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ def test_unix_udp(self):
+ with unix_socket_path() as name:
+ with closing(bind_unix_socket(name, type=SOCK_STREAM)) as sock:
+ conn = self.check_socket(sock)
+ assert not conn.raddr
+ self.assertEqual(conn.status, psutil.CONN_NONE)
+
+
+# =====================================================================
+# --- Test connected sockets
+# =====================================================================
+
+
+class TestConnectedSocketPairs(Base, unittest.TestCase):
+ """Test socket pairs which are are actually connected to
+ each other.
+ """
+
+ # On SunOS, even after we close() it, the server socket stays around
+ # in TIME_WAIT state.
+ @unittest.skipIf(SUNOS, "unreliable on SUONS")
+ def test_tcp(self):
+ addr = ("127.0.0.1", get_free_port())
+ assert not thisproc.connections(kind='tcp4')
+ server, client = tcp_socketpair(AF_INET, addr=addr)
+ try:
+ cons = thisproc.connections(kind='tcp4')
+ self.assertEqual(len(cons), 2)
+ self.assertEqual(cons[0].status, psutil.CONN_ESTABLISHED)
+ self.assertEqual(cons[1].status, psutil.CONN_ESTABLISHED)
+ # May not be fast enough to change state so it stays
+ # commenteed.
+ # client.close()
+ # cons = thisproc.connections(kind='all')
+ # self.assertEqual(len(cons), 1)
+ # self.assertEqual(cons[0].status, psutil.CONN_CLOSE_WAIT)
+ finally:
+ server.close()
+ client.close()
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ def test_unix(self):
+ with unix_socket_path() as name:
+ server, client = unix_socketpair(name)
+ try:
+ cons = thisproc.connections(kind='unix')
+ assert not (cons[0].laddr and cons[0].raddr)
+ assert not (cons[1].laddr and cons[1].raddr)
+ if NETBSD:
+ # On NetBSD creating a UNIX socket will cause
+ # a UNIX connection to /var/run/log.
+ cons = [c for c in cons if c.raddr != '/var/run/log']
+ self.assertEqual(len(cons), 2)
+ if LINUX or FREEBSD or SUNOS:
+ # remote path is never set
+ self.assertEqual(cons[0].raddr, "")
+ self.assertEqual(cons[1].raddr, "")
+ # one local address should though
+ self.assertEqual(name, cons[0].laddr or cons[1].laddr)
+ elif OPENBSD:
+ # No addresses whatsoever here.
+ for addr in (cons[0].laddr, cons[0].raddr,
+ cons[1].laddr, cons[1].raddr):
+ self.assertEqual(addr, "")
+ else:
+ # On other systems either the laddr or raddr
+ # of both peers are set.
+ self.assertEqual(cons[0].laddr or cons[1].laddr, name)
+ self.assertEqual(cons[0].raddr or cons[1].raddr, name)
+ finally:
+ server.close()
+ client.close()
+
+ @skip_on_access_denied(only_if=OSX)
+ def test_combos(self):
+ def check_conn(proc, conn, family, type, laddr, raddr, status, kinds):
+ all_kinds = ("all", "inet", "inet4", "inet6", "tcp", "tcp4",
+ "tcp6", "udp", "udp4", "udp6")
+ check_connection_ntuple(conn)
+ self.assertEqual(conn.family, family)
+ self.assertEqual(conn.type, type)
+ self.assertEqual(conn.laddr, laddr)
+ self.assertEqual(conn.raddr, raddr)
+ self.assertEqual(conn.status, status)
+ for kind in all_kinds:
+ cons = proc.connections(kind=kind)
+ if kind in kinds:
+ assert cons
+ else:
+ assert not cons, cons
+ # compare against system-wide connections
+ # XXX Solaris can't retrieve system-wide UNIX
+ # sockets.
+ if HAS_CONNECTIONS_UNIX:
+ self.compare_procsys_connections(proc.pid, [conn])
+
+ tcp_template = textwrap.dedent("""
+ import socket, time
+ s = socket.socket($family, socket.SOCK_STREAM)
+ s.bind(('$addr', 0))
+ s.listen(1)
+ with open('$testfn', 'w') as f:
+ f.write(str(s.getsockname()[:2]))
+ time.sleep(60)
+ """)
+
+ udp_template = textwrap.dedent("""
+ import socket, time
+ s = socket.socket($family, socket.SOCK_DGRAM)
+ s.bind(('$addr', 0))
+ with open('$testfn', 'w') as f:
+ f.write(str(s.getsockname()[:2]))
+ time.sleep(60)
+ """)
+
+ from string import Template
+ testfile = os.path.basename(TESTFN)
+ tcp4_template = Template(tcp_template).substitute(
+ family=int(AF_INET), addr="127.0.0.1", testfn=testfile)
+ udp4_template = Template(udp_template).substitute(
+ family=int(AF_INET), addr="127.0.0.1", testfn=testfile)
+ tcp6_template = Template(tcp_template).substitute(
+ family=int(AF_INET6), addr="::1", testfn=testfile)
+ udp6_template = Template(udp_template).substitute(
+ family=int(AF_INET6), addr="::1", testfn=testfile)
+
+ # launch various subprocess instantiating a socket of various
+ # families and types to enrich psutil results
+ tcp4_proc = pyrun(tcp4_template)
+ tcp4_addr = eval(wait_for_file(testfile))
+ udp4_proc = pyrun(udp4_template)
+ udp4_addr = eval(wait_for_file(testfile))
+ if supports_ipv6():
+ tcp6_proc = pyrun(tcp6_template)
+ tcp6_addr = eval(wait_for_file(testfile))
+ udp6_proc = pyrun(udp6_template)
+ udp6_addr = eval(wait_for_file(testfile))
+ else:
+ tcp6_proc = None
+ udp6_proc = None
+ tcp6_addr = None
+ udp6_addr = None
+
+ for p in thisproc.children():
+ cons = p.connections()
+ self.assertEqual(len(cons), 1)
+ for conn in cons:
+ # TCP v4
+ if p.pid == tcp4_proc.pid:
+ check_conn(p, conn, AF_INET, SOCK_STREAM, tcp4_addr, (),
+ psutil.CONN_LISTEN,
+ ("all", "inet", "inet4", "tcp", "tcp4"))
+ # UDP v4
+ elif p.pid == udp4_proc.pid:
+ check_conn(p, conn, AF_INET, SOCK_DGRAM, udp4_addr, (),
+ psutil.CONN_NONE,
+ ("all", "inet", "inet4", "udp", "udp4"))
+ # TCP v6
+ elif p.pid == getattr(tcp6_proc, "pid", None):
+ check_conn(p, conn, AF_INET6, SOCK_STREAM, tcp6_addr, (),
+ psutil.CONN_LISTEN,
+ ("all", "inet", "inet6", "tcp", "tcp6"))
+ # UDP v6
+ elif p.pid == getattr(udp6_proc, "pid", None):
+ check_conn(p, conn, AF_INET6, SOCK_DGRAM, udp6_addr, (),
+ psutil.CONN_NONE,
+ ("all", "inet", "inet6", "udp", "udp6"))
+
+ # err
+ self.assertRaises(ValueError, p.connections, kind='???')
+
+ def test_multi_sockets_filtering(self):
+ with create_sockets() as socks:
+ cons = thisproc.connections(kind='all')
+ self.assertEqual(len(cons), len(socks))
+ # tcp
+ cons = thisproc.connections(kind='tcp')
+ self.assertEqual(len(cons), 2 if supports_ipv6() else 1)
+ for conn in cons:
+ self.assertIn(conn.family, (AF_INET, AF_INET6))
+ self.assertEqual(conn.type, SOCK_STREAM)
+ # tcp4
+ cons = thisproc.connections(kind='tcp4')
+ self.assertEqual(len(cons), 1)
+ self.assertEqual(cons[0].family, AF_INET)
+ self.assertEqual(cons[0].type, SOCK_STREAM)
+ # tcp6
+ if supports_ipv6():
+ cons = thisproc.connections(kind='tcp6')
+ self.assertEqual(len(cons), 1)
+ self.assertEqual(cons[0].family, AF_INET6)
+ self.assertEqual(cons[0].type, SOCK_STREAM)
+ # udp
+ cons = thisproc.connections(kind='udp')
+ self.assertEqual(len(cons), 2 if supports_ipv6() else 1)
+ for conn in cons:
+ self.assertIn(conn.family, (AF_INET, AF_INET6))
+ self.assertEqual(conn.type, SOCK_DGRAM)
+ # udp4
+ cons = thisproc.connections(kind='udp4')
+ self.assertEqual(len(cons), 1)
+ self.assertEqual(cons[0].family, AF_INET)
+ self.assertEqual(cons[0].type, SOCK_DGRAM)
+ # udp6
+ if supports_ipv6():
+ cons = thisproc.connections(kind='udp6')
+ self.assertEqual(len(cons), 1)
+ self.assertEqual(cons[0].family, AF_INET6)
+ self.assertEqual(cons[0].type, SOCK_DGRAM)
+ # inet
+ cons = thisproc.connections(kind='inet')
+ self.assertEqual(len(cons), 4 if supports_ipv6() else 2)
+ for conn in cons:
+ self.assertIn(conn.family, (AF_INET, AF_INET6))
+ self.assertIn(conn.type, (SOCK_STREAM, SOCK_DGRAM))
+ # inet6
+ if supports_ipv6():
+ cons = thisproc.connections(kind='inet6')
+ self.assertEqual(len(cons), 2)
+ for conn in cons:
+ self.assertEqual(conn.family, AF_INET6)
+ self.assertIn(conn.type, (SOCK_STREAM, SOCK_DGRAM))
+ # unix
+ if HAS_CONNECTIONS_UNIX:
+ cons = thisproc.connections(kind='unix')
+ self.assertEqual(len(cons), 3)
+ for conn in cons:
+ self.assertEqual(conn.family, AF_UNIX)
+ self.assertIn(conn.type, (SOCK_STREAM, SOCK_DGRAM))
+
+
+# =====================================================================
+# --- Miscellaneous tests
+# =====================================================================
+
+
+class TestSystemWideConnections(Base, unittest.TestCase):
+ """Tests for net_connections()."""
+
+ @skip_on_access_denied()
+ def test_it(self):
+ def check(cons, families, types_):
+ AF_UNIX = getattr(socket, 'AF_UNIX', object())
+ for conn in cons:
+ self.assertIn(conn.family, families, msg=conn)
+ if conn.family != AF_UNIX:
+ self.assertIn(conn.type, types_, msg=conn)
+ check_connection_ntuple(conn)
+
+ with create_sockets():
+ from psutil._common import conn_tmap
+ for kind, groups in conn_tmap.items():
+ # XXX: SunOS does not retrieve UNIX sockets.
+ if kind == 'unix' and not HAS_CONNECTIONS_UNIX:
+ continue
+ families, types_ = groups
+ cons = psutil.net_connections(kind)
+ self.assertEqual(len(cons), len(set(cons)))
+ check(cons, families, types_)
+
+ self.assertRaises(ValueError, psutil.net_connections, kind='???')
+
+ @skip_on_access_denied()
+ def test_multi_socks(self):
+ with create_sockets() as socks:
+ cons = [x for x in psutil.net_connections(kind='all')
+ if x.pid == os.getpid()]
+ self.assertEqual(len(cons), len(socks))
+
+ @skip_on_access_denied()
+ # See: https://travis-ci.org/giampaolo/psutil/jobs/237566297
+ @unittest.skipIf(OSX and TRAVIS, "unreliable on OSX + TRAVIS")
+ def test_multi_sockets_procs(self):
+ # Creates multiple sub processes, each creating different
+ # sockets. For each process check that proc.connections()
+ # and net_connections() return the same results.
+ # This is done mainly to check whether net_connections()'s
+ # pid is properly set, see:
+ # https://github.com/giampaolo/psutil/issues/1013
+ with create_sockets() as socks:
+ expected = len(socks)
+ pids = []
+ times = 10
+ for i in range(times):
+ fname = os.path.realpath(TESTFN) + str(i)
+ src = textwrap.dedent("""\
+ import time, os
+ from psutil.tests import create_sockets
+ with create_sockets():
+ with open('%s', 'w') as f:
+ f.write(str(os.getpid()))
+ time.sleep(60)
+ """ % fname)
+ sproc = pyrun(src)
+ pids.append(sproc.pid)
+ self.addCleanup(safe_rmpath, fname)
+
+ # sync
+ for i in range(times):
+ fname = TESTFN + str(i)
+ wait_for_file(fname)
+
+ syscons = [x for x in psutil.net_connections(kind='all') if x.pid
+ in pids]
+ for pid in pids:
+ self.assertEqual(len([x for x in syscons if x.pid == pid]),
+ expected)
+ p = psutil.Process(pid)
+ self.assertEqual(len(p.connections('all')), expected)
+
+
+# =====================================================================
+# --- Miscellaneous tests
+# =====================================================================
+
+
+class TestMisc(unittest.TestCase):
+
+ def test_connection_constants(self):
+ ints = []
+ strs = []
+ for name in dir(psutil):
+ if name.startswith('CONN_'):
+ num = getattr(psutil, name)
+ str_ = str(num)
+ assert str_.isupper(), str_
+ self.assertNotIn(str, strs)
+ self.assertNotIn(num, ints)
+ ints.append(num)
+ strs.append(str_)
+ if SUNOS:
+ psutil.CONN_IDLE
+ psutil.CONN_BOUND
+ if WINDOWS:
+ psutil.CONN_DELETE_TCB
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py
new file mode 100755
index 00000000..0dfb3e2a
--- /dev/null
+++ b/psutil/tests/test_contracts.py
@@ -0,0 +1,650 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Contracts tests. These tests mainly check API sanity in terms of
+returned types and APIs availability.
+Some of these are duplicates of tests test_system.py and test_process.py
+"""
+
+import errno
+import os
+import stat
+import time
+import traceback
+import warnings
+from contextlib import closing
+
+from psutil import AIX
+from psutil import BSD
+from psutil import FREEBSD
+from psutil import LINUX
+from psutil import NETBSD
+from psutil import OPENBSD
+from psutil import OSX
+from psutil import POSIX
+from psutil import SUNOS
+from psutil import WINDOWS
+from psutil._compat import long
+from psutil.tests import bind_unix_socket
+from psutil.tests import check_connection_ntuple
+from psutil.tests import get_kernel_version
+from psutil.tests import HAS_CONNECTIONS_UNIX
+from psutil.tests import HAS_RLIMIT
+from psutil.tests import HAS_SENSORS_FANS
+from psutil.tests import HAS_SENSORS_TEMPERATURES
+from psutil.tests import is_namedtuple
+from psutil.tests import run_test_module_by_name
+from psutil.tests import safe_rmpath
+from psutil.tests import skip_on_access_denied
+from psutil.tests import TESTFN
+from psutil.tests import unittest
+from psutil.tests import unix_socket_path
+from psutil.tests import VALID_PROC_STATUSES
+from psutil.tests import warn
+import psutil
+
+
+# ===================================================================
+# --- APIs availability
+# ===================================================================
+
+
+class TestAvailability(unittest.TestCase):
+ """Make sure code reflects what doc promises in terms of APIs
+ availability.
+ """
+
+ def test_cpu_affinity(self):
+ hasit = LINUX or WINDOWS or FREEBSD
+ self.assertEqual(hasattr(psutil.Process, "cpu_affinity"), hasit)
+
+ def test_win_service(self):
+ self.assertEqual(hasattr(psutil, "win_service_iter"), WINDOWS)
+ self.assertEqual(hasattr(psutil, "win_service_get"), WINDOWS)
+
+ def test_PROCFS_PATH(self):
+ self.assertEqual(hasattr(psutil, "PROCFS_PATH"),
+ LINUX or SUNOS or AIX)
+
+ def test_win_priority(self):
+ ae = self.assertEqual
+ ae(hasattr(psutil, "ABOVE_NORMAL_PRIORITY_CLASS"), WINDOWS)
+ ae(hasattr(psutil, "BELOW_NORMAL_PRIORITY_CLASS"), WINDOWS)
+ ae(hasattr(psutil, "HIGH_PRIORITY_CLASS"), WINDOWS)
+ ae(hasattr(psutil, "IDLE_PRIORITY_CLASS"), WINDOWS)
+ ae(hasattr(psutil, "NORMAL_PRIORITY_CLASS"), WINDOWS)
+ ae(hasattr(psutil, "REALTIME_PRIORITY_CLASS"), WINDOWS)
+
+ def test_linux_ioprio(self):
+ ae = self.assertEqual
+ ae(hasattr(psutil, "IOPRIO_CLASS_NONE"), LINUX)
+ ae(hasattr(psutil, "IOPRIO_CLASS_RT"), LINUX)
+ ae(hasattr(psutil, "IOPRIO_CLASS_BE"), LINUX)
+ ae(hasattr(psutil, "IOPRIO_CLASS_IDLE"), LINUX)
+
+ def test_linux_rlimit(self):
+ ae = self.assertEqual
+ hasit = LINUX and get_kernel_version() >= (2, 6, 36)
+ ae(hasattr(psutil.Process, "rlimit"), hasit)
+ ae(hasattr(psutil, "RLIM_INFINITY"), hasit)
+ ae(hasattr(psutil, "RLIMIT_AS"), hasit)
+ ae(hasattr(psutil, "RLIMIT_CORE"), hasit)
+ ae(hasattr(psutil, "RLIMIT_CPU"), hasit)
+ ae(hasattr(psutil, "RLIMIT_DATA"), hasit)
+ ae(hasattr(psutil, "RLIMIT_FSIZE"), hasit)
+ ae(hasattr(psutil, "RLIMIT_LOCKS"), hasit)
+ ae(hasattr(psutil, "RLIMIT_MEMLOCK"), hasit)
+ ae(hasattr(psutil, "RLIMIT_NOFILE"), hasit)
+ ae(hasattr(psutil, "RLIMIT_NPROC"), hasit)
+ ae(hasattr(psutil, "RLIMIT_RSS"), hasit)
+ ae(hasattr(psutil, "RLIMIT_STACK"), hasit)
+
+ hasit = LINUX and get_kernel_version() >= (3, 0)
+ ae(hasattr(psutil, "RLIMIT_MSGQUEUE"), hasit)
+ ae(hasattr(psutil, "RLIMIT_NICE"), hasit)
+ ae(hasattr(psutil, "RLIMIT_RTPRIO"), hasit)
+ ae(hasattr(psutil, "RLIMIT_RTTIME"), hasit)
+ ae(hasattr(psutil, "RLIMIT_SIGPENDING"), hasit)
+
+ def test_cpu_freq(self):
+ linux = (LINUX and
+ (os.path.exists("/sys/devices/system/cpu/cpufreq") or
+ os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq")))
+ self.assertEqual(hasattr(psutil, "cpu_freq"), linux or OSX or WINDOWS)
+
+ def test_sensors_temperatures(self):
+ self.assertEqual(hasattr(psutil, "sensors_temperatures"), LINUX)
+
+ def test_sensors_fans(self):
+ self.assertEqual(hasattr(psutil, "sensors_fans"), LINUX)
+
+ def test_battery(self):
+ self.assertEqual(hasattr(psutil, "sensors_battery"),
+ LINUX or WINDOWS or FREEBSD or OSX)
+
+ def test_proc_environ(self):
+ self.assertEqual(hasattr(psutil.Process, "environ"),
+ LINUX or OSX or WINDOWS)
+
+ def test_proc_uids(self):
+ self.assertEqual(hasattr(psutil.Process, "uids"), POSIX)
+
+ def test_proc_gids(self):
+ self.assertEqual(hasattr(psutil.Process, "uids"), POSIX)
+
+ def test_proc_terminal(self):
+ self.assertEqual(hasattr(psutil.Process, "terminal"), POSIX)
+
+ def test_proc_ionice(self):
+ self.assertEqual(hasattr(psutil.Process, "ionice"), LINUX or WINDOWS)
+
+ def test_proc_rlimit(self):
+ self.assertEqual(hasattr(psutil.Process, "rlimit"), LINUX)
+
+ def test_proc_io_counters(self):
+ hasit = hasattr(psutil.Process, "io_counters")
+ self.assertEqual(hasit, False if OSX or SUNOS else True)
+
+ def test_proc_num_fds(self):
+ self.assertEqual(hasattr(psutil.Process, "num_fds"), POSIX)
+
+ def test_proc_num_handles(self):
+ self.assertEqual(hasattr(psutil.Process, "num_handles"), WINDOWS)
+
+ def test_proc_cpu_affinity(self):
+ self.assertEqual(hasattr(psutil.Process, "cpu_affinity"),
+ LINUX or WINDOWS or FREEBSD)
+
+ def test_proc_cpu_num(self):
+ self.assertEqual(hasattr(psutil.Process, "cpu_num"),
+ LINUX or FREEBSD or SUNOS)
+
+ def test_proc_memory_maps(self):
+ hasit = hasattr(psutil.Process, "memory_maps")
+ self.assertEqual(hasit, False if OPENBSD or NETBSD or AIX else True)
+
+
+# ===================================================================
+# --- Test deprecations
+# ===================================================================
+
+
+class TestDeprecations(unittest.TestCase):
+
+ def test_memory_info_ex(self):
+ with warnings.catch_warnings(record=True) as ws:
+ psutil.Process().memory_info_ex()
+ w = ws[0]
+ self.assertIsInstance(w.category(), FutureWarning)
+ self.assertIn("memory_info_ex() is deprecated", str(w.message))
+ self.assertIn("use memory_info() instead", str(w.message))
+
+
+# ===================================================================
+# --- System API types
+# ===================================================================
+
+
+class TestSystem(unittest.TestCase):
+ """Check the return types of system related APIs.
+ Mainly we want to test we never return unicode on Python 2, see:
+ https://github.com/giampaolo/psutil/issues/1039
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ cls.proc = psutil.Process()
+
+ def tearDown(self):
+ safe_rmpath(TESTFN)
+
+ def test_cpu_times(self):
+ # Duplicate of test_system.py. Keep it anyway.
+ ret = psutil.cpu_times()
+ assert is_namedtuple(ret)
+ for n in ret:
+ self.assertIsInstance(n, float)
+ self.assertGreaterEqual(n, 0)
+
+ def test_io_counters(self):
+ # Duplicate of test_system.py. Keep it anyway.
+ for k in psutil.disk_io_counters(perdisk=True):
+ self.assertIsInstance(k, str)
+
+ def test_disk_partitions(self):
+ # Duplicate of test_system.py. Keep it anyway.
+ for disk in psutil.disk_partitions():
+ self.assertIsInstance(disk.device, str)
+ self.assertIsInstance(disk.mountpoint, str)
+ self.assertIsInstance(disk.fstype, str)
+ self.assertIsInstance(disk.opts, str)
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ @unittest.skipIf(not HAS_CONNECTIONS_UNIX, "can't list UNIX sockets")
+ @skip_on_access_denied(only_if=OSX)
+ def test_net_connections(self):
+ with unix_socket_path() as name:
+ with closing(bind_unix_socket(name)):
+ cons = psutil.net_connections(kind='unix')
+ assert cons
+ for conn in cons:
+ self.assertIsInstance(conn.laddr, str)
+
+ def test_net_if_addrs(self):
+ # Duplicate of test_system.py. Keep it anyway.
+ for ifname, addrs in psutil.net_if_addrs().items():
+ self.assertIsInstance(ifname, str)
+ for addr in addrs:
+ self.assertIsInstance(addr.address, str)
+ self.assertIsInstance(addr.netmask, (str, type(None)))
+ self.assertIsInstance(addr.broadcast, (str, type(None)))
+
+ def test_net_if_stats(self):
+ # Duplicate of test_system.py. Keep it anyway.
+ for ifname, _ in psutil.net_if_stats().items():
+ self.assertIsInstance(ifname, str)
+
+ def test_net_io_counters(self):
+ # Duplicate of test_system.py. Keep it anyway.
+ for ifname, _ in psutil.net_io_counters(pernic=True).items():
+ self.assertIsInstance(ifname, str)
+
+ @unittest.skipIf(not HAS_SENSORS_FANS, "not supported")
+ def test_sensors_fans(self):
+ # Duplicate of test_system.py. Keep it anyway.
+ for name, units in psutil.sensors_fans().items():
+ self.assertIsInstance(name, str)
+ for unit in units:
+ self.assertIsInstance(unit.label, str)
+
+ @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported")
+ def test_sensors_temperatures(self):
+ # Duplicate of test_system.py. Keep it anyway.
+ for name, units in psutil.sensors_temperatures().items():
+ self.assertIsInstance(name, str)
+ for unit in units:
+ self.assertIsInstance(unit.label, str)
+
+ def test_users(self):
+ # Duplicate of test_system.py. Keep it anyway.
+ for user in psutil.users():
+ self.assertIsInstance(user.name, str)
+ self.assertIsInstance(user.terminal, (str, type(None)))
+ self.assertIsInstance(user.host, (str, type(None)))
+ self.assertIsInstance(user.pid, (int, type(None)))
+
+
+# ===================================================================
+# --- Featch all processes test
+# ===================================================================
+
+
+class TestFetchAllProcesses(unittest.TestCase):
+ """Test which iterates over all running processes and performs
+ some sanity checks against Process API's returned values.
+ """
+
+ def setUp(self):
+ if POSIX:
+ import pwd
+ import grp
+ users = pwd.getpwall()
+ groups = grp.getgrall()
+ self.all_uids = set([x.pw_uid for x in users])
+ self.all_usernames = set([x.pw_name for x in users])
+ self.all_gids = set([x.gr_gid for x in groups])
+
+ def test_fetch_all(self):
+ valid_procs = 0
+ excluded_names = set([
+ 'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait',
+ 'as_dict', 'parent', 'children', 'memory_info_ex', 'oneshot',
+ ])
+ if LINUX and not HAS_RLIMIT:
+ excluded_names.add('rlimit')
+ attrs = []
+ for name in dir(psutil.Process):
+ if name.startswith("_"):
+ continue
+ if name in excluded_names:
+ continue
+ attrs.append(name)
+
+ default = object()
+ failures = []
+ for p in psutil.process_iter():
+ with p.oneshot():
+ for name in attrs:
+ ret = default
+ try:
+ args = ()
+ kwargs = {}
+ attr = getattr(p, name, None)
+ if attr is not None and callable(attr):
+ if name == 'rlimit':
+ args = (psutil.RLIMIT_NOFILE,)
+ elif name == 'memory_maps':
+ kwargs = {'grouped': False}
+ ret = attr(*args, **kwargs)
+ else:
+ ret = attr
+ valid_procs += 1
+ except NotImplementedError:
+ msg = "%r was skipped because not implemented" % (
+ self.__class__.__name__ + '.test_' + name)
+ warn(msg)
+ except (psutil.NoSuchProcess, psutil.AccessDenied) as err:
+ self.assertEqual(err.pid, p.pid)
+ if err.name:
+ # make sure exception's name attr is set
+ # with the actual process name
+ self.assertEqual(err.name, p.name())
+ assert str(err)
+ assert err.msg
+ except Exception as err:
+ s = '\n' + '=' * 70 + '\n'
+ s += "FAIL: test_%s (proc=%s" % (name, p)
+ if ret != default:
+ s += ", ret=%s)" % repr(ret)
+ s += ')\n'
+ s += '-' * 70
+ s += "\n%s" % traceback.format_exc()
+ s = "\n".join((" " * 4) + i for i in s.splitlines())
+ s += '\n'
+ failures.append(s)
+ break
+ else:
+ if ret not in (0, 0.0, [], None, '', {}):
+ assert ret, ret
+ meth = getattr(self, name)
+ meth(ret, p)
+
+ if failures:
+ self.fail(''.join(failures))
+
+ # we should always have a non-empty list, not including PID 0 etc.
+ # special cases.
+ assert valid_procs
+
+ def cmdline(self, ret, proc):
+ self.assertIsInstance(ret, list)
+ for part in ret:
+ self.assertIsInstance(part, str)
+
+ def exe(self, ret, proc):
+ self.assertIsInstance(ret, (str, type(None)))
+ if not ret:
+ self.assertEqual(ret, '')
+ else:
+ assert os.path.isabs(ret), ret
+ # Note: os.stat() may return False even if the file is there
+ # hence we skip the test, see:
+ # http://stackoverflow.com/questions/3112546/os-path-exists-lies
+ if POSIX and os.path.isfile(ret):
+ if hasattr(os, 'access') and hasattr(os, "X_OK"):
+ # XXX may fail on OSX
+ assert os.access(ret, os.X_OK)
+
+ def pid(self, ret, proc):
+ self.assertIsInstance(ret, int)
+ self.assertGreaterEqual(ret, 0)
+
+ def ppid(self, ret, proc):
+ self.assertIsInstance(ret, (int, long))
+ self.assertGreaterEqual(ret, 0)
+
+ def name(self, ret, proc):
+ self.assertIsInstance(ret, str)
+ # on AIX, "<exiting>" processes don't have names
+ if not AIX:
+ assert ret
+
+ def create_time(self, ret, proc):
+ self.assertIsInstance(ret, float)
+ try:
+ self.assertGreaterEqual(ret, 0)
+ except AssertionError:
+ # XXX
+ if OPENBSD and proc.status() == psutil.STATUS_ZOMBIE:
+ pass
+ else:
+ raise
+ # this can't be taken for granted on all platforms
+ # self.assertGreaterEqual(ret, psutil.boot_time())
+ # make sure returned value can be pretty printed
+ # with strftime
+ time.strftime("%Y %m %d %H:%M:%S", time.localtime(ret))
+
+ def uids(self, ret, proc):
+ assert is_namedtuple(ret)
+ for uid in ret:
+ self.assertIsInstance(uid, int)
+ self.assertGreaterEqual(uid, 0)
+ self.assertIn(uid, self.all_uids)
+
+ def gids(self, ret, proc):
+ assert is_namedtuple(ret)
+ # note: testing all gids as above seems not to be reliable for
+ # gid == 30 (nodoby); not sure why.
+ for gid in ret:
+ self.assertIsInstance(gid, int)
+ if not OSX and not NETBSD:
+ self.assertGreaterEqual(gid, 0)
+ self.assertIn(gid, self.all_gids)
+
+ def username(self, ret, proc):
+ self.assertIsInstance(ret, str)
+ assert ret
+ if POSIX:
+ self.assertIn(ret, self.all_usernames)
+
+ def status(self, ret, proc):
+ self.assertIsInstance(ret, str)
+ assert ret
+ self.assertNotEqual(ret, '?') # XXX
+ self.assertIn(ret, VALID_PROC_STATUSES)
+
+ def io_counters(self, ret, proc):
+ assert is_namedtuple(ret)
+ for field in ret:
+ self.assertIsInstance(field, (int, long))
+ if field != -1:
+ self.assertGreaterEqual(field, 0)
+
+ def ionice(self, ret, proc):
+ if POSIX:
+ assert is_namedtuple(ret)
+ for field in ret:
+ self.assertIsInstance(field, int)
+ if LINUX:
+ self.assertGreaterEqual(ret.ioclass, 0)
+ self.assertGreaterEqual(ret.value, 0)
+ else:
+ self.assertGreaterEqual(ret, 0)
+ self.assertIn(ret, (0, 1, 2))
+
+ def num_threads(self, ret, proc):
+ self.assertIsInstance(ret, int)
+ self.assertGreaterEqual(ret, 1)
+
+ def threads(self, ret, proc):
+ self.assertIsInstance(ret, list)
+ for t in ret:
+ assert is_namedtuple(t)
+ self.assertGreaterEqual(t.id, 0)
+ self.assertGreaterEqual(t.user_time, 0)
+ self.assertGreaterEqual(t.system_time, 0)
+ for field in t:
+ self.assertIsInstance(field, (int, float))
+
+ def cpu_times(self, ret, proc):
+ assert is_namedtuple(ret)
+ for n in ret:
+ self.assertIsInstance(n, float)
+ self.assertGreaterEqual(n, 0)
+ # TODO: check ntuple fields
+
+ def cpu_percent(self, ret, proc):
+ self.assertIsInstance(ret, float)
+ assert 0.0 <= ret <= 100.0, ret
+
+ def cpu_num(self, ret, proc):
+ self.assertIsInstance(ret, int)
+ if FREEBSD and ret == -1:
+ return
+ self.assertGreaterEqual(ret, 0)
+ if psutil.cpu_count() == 1:
+ self.assertEqual(ret, 0)
+ self.assertIn(ret, list(range(psutil.cpu_count())))
+
+ def memory_info(self, ret, proc):
+ assert is_namedtuple(ret)
+ for value in ret:
+ self.assertIsInstance(value, (int, long))
+ self.assertGreaterEqual(value, 0)
+ if POSIX and not AIX and ret.vms != 0:
+ # VMS is always supposed to be the highest
+ for name in ret._fields:
+ if name != 'vms':
+ value = getattr(ret, name)
+ self.assertGreater(ret.vms, value, msg=ret)
+ elif WINDOWS:
+ self.assertGreaterEqual(ret.peak_wset, ret.wset)
+ self.assertGreaterEqual(ret.peak_paged_pool, ret.paged_pool)
+ self.assertGreaterEqual(ret.peak_nonpaged_pool, ret.nonpaged_pool)
+ self.assertGreaterEqual(ret.peak_pagefile, ret.pagefile)
+
+ def memory_full_info(self, ret, proc):
+ assert is_namedtuple(ret)
+ total = psutil.virtual_memory().total
+ for name in ret._fields:
+ value = getattr(ret, name)
+ self.assertIsInstance(value, (int, long))
+ self.assertGreaterEqual(value, 0, msg=(name, value))
+ self.assertLessEqual(value, total, msg=(name, value, total))
+
+ if LINUX:
+ self.assertGreaterEqual(ret.pss, ret.uss)
+
+ def open_files(self, ret, proc):
+ self.assertIsInstance(ret, list)
+ for f in ret:
+ self.assertIsInstance(f.fd, int)
+ self.assertIsInstance(f.path, str)
+ if WINDOWS:
+ self.assertEqual(f.fd, -1)
+ elif LINUX:
+ self.assertIsInstance(f.position, int)
+ self.assertIsInstance(f.mode, str)
+ self.assertIsInstance(f.flags, int)
+ self.assertGreaterEqual(f.position, 0)
+ self.assertIn(f.mode, ('r', 'w', 'a', 'r+', 'a+'))
+ self.assertGreater(f.flags, 0)
+ elif BSD and not f.path:
+ # XXX see: https://github.com/giampaolo/psutil/issues/595
+ continue
+ assert os.path.isabs(f.path), f
+ assert os.path.isfile(f.path), f
+
+ def num_fds(self, ret, proc):
+ self.assertIsInstance(ret, int)
+ self.assertGreaterEqual(ret, 0)
+
+ def connections(self, ret, proc):
+ self.assertEqual(len(ret), len(set(ret)))
+ for conn in ret:
+ check_connection_ntuple(conn)
+
+ def cwd(self, ret, proc):
+ if ret: # 'ret' can be None or empty
+ self.assertIsInstance(ret, str)
+ assert os.path.isabs(ret), ret
+ try:
+ st = os.stat(ret)
+ except OSError as err:
+ if WINDOWS and err.errno in \
+ psutil._psplatform.ACCESS_DENIED_SET:
+ pass
+ # directory has been removed in mean time
+ elif err.errno != errno.ENOENT:
+ raise
+ else:
+ assert stat.S_ISDIR(st.st_mode)
+
+ def memory_percent(self, ret, proc):
+ self.assertIsInstance(ret, float)
+ assert 0 <= ret <= 100, ret
+
+ def is_running(self, ret, proc):
+ self.assertIsInstance(ret, bool)
+
+ def cpu_affinity(self, ret, proc):
+ self.assertIsInstance(ret, list)
+ assert ret != [], ret
+ cpus = range(psutil.cpu_count())
+ for n in ret:
+ self.assertIsInstance(n, int)
+ self.assertIn(n, cpus)
+
+ def terminal(self, ret, proc):
+ self.assertIsInstance(ret, (str, type(None)))
+ if ret is not None:
+ assert os.path.isabs(ret), ret
+ assert os.path.exists(ret), ret
+
+ def memory_maps(self, ret, proc):
+ for nt in ret:
+ self.assertIsInstance(nt.addr, str)
+ self.assertIsInstance(nt.perms, str)
+ self.assertIsInstance(nt.path, str)
+ for fname in nt._fields:
+ value = getattr(nt, fname)
+ if fname == 'path':
+ if not value.startswith('['):
+ assert os.path.isabs(nt.path), nt.path
+ # commented as on Linux we might get
+ # '/foo/bar (deleted)'
+ # assert os.path.exists(nt.path), nt.path
+ elif fname in ('addr', 'perms'):
+ assert value
+ else:
+ self.assertIsInstance(value, (int, long))
+ self.assertGreaterEqual(value, 0)
+
+ def num_handles(self, ret, proc):
+ self.assertIsInstance(ret, int)
+ self.assertGreaterEqual(ret, 0)
+
+ def nice(self, ret, proc):
+ self.assertIsInstance(ret, int)
+ if POSIX:
+ assert -20 <= ret <= 20, ret
+ else:
+ priorities = [getattr(psutil, x) for x in dir(psutil)
+ if x.endswith('_PRIORITY_CLASS')]
+ self.assertIn(ret, priorities)
+
+ def num_ctx_switches(self, ret, proc):
+ assert is_namedtuple(ret)
+ for value in ret:
+ self.assertIsInstance(value, (int, long))
+ self.assertGreaterEqual(value, 0)
+
+ def rlimit(self, ret, proc):
+ self.assertIsInstance(ret, tuple)
+ self.assertEqual(len(ret), 2)
+ self.assertGreaterEqual(ret[0], -1)
+ self.assertGreaterEqual(ret[1], -1)
+
+ def environ(self, ret, proc):
+ self.assertIsInstance(ret, dict)
+ for k, v in ret.items():
+ self.assertIsInstance(k, str)
+ self.assertIsInstance(v, str)
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py
new file mode 100755
index 00000000..6ba17b25
--- /dev/null
+++ b/psutil/tests/test_linux.py
@@ -0,0 +1,1910 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Linux specific tests."""
+
+from __future__ import division
+import collections
+import contextlib
+import errno
+import glob
+import io
+import os
+import pprint
+import re
+import shutil
+import socket
+import struct
+import tempfile
+import textwrap
+import time
+import warnings
+
+import psutil
+from psutil import LINUX
+from psutil._compat import PY3
+from psutil._compat import u
+from psutil.tests import call_until
+from psutil.tests import HAS_BATTERY
+from psutil.tests import HAS_CPU_FREQ
+from psutil.tests import HAS_RLIMIT
+from psutil.tests import MEMORY_TOLERANCE
+from psutil.tests import mock
+from psutil.tests import PYPY
+from psutil.tests import pyrun
+from psutil.tests import reap_children
+from psutil.tests import reload_module
+from psutil.tests import retry_before_failing
+from psutil.tests import run_test_module_by_name
+from psutil.tests import safe_rmpath
+from psutil.tests import sh
+from psutil.tests import skip_on_not_implemented
+from psutil.tests import TESTFN
+from psutil.tests import ThreadTask
+from psutil.tests import TRAVIS
+from psutil.tests import unittest
+from psutil.tests import which
+
+
+HERE = os.path.abspath(os.path.dirname(__file__))
+SIOCGIFADDR = 0x8915
+SIOCGIFCONF = 0x8912
+SIOCGIFHWADDR = 0x8927
+if LINUX:
+ SECTOR_SIZE = 512
+
+
+# =====================================================================
+# --- utils
+# =====================================================================
+
+
+def get_ipv4_address(ifname):
+ import fcntl
+ ifname = ifname[:15]
+ if PY3:
+ ifname = bytes(ifname, 'ascii')
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ with contextlib.closing(s):
+ return socket.inet_ntoa(
+ fcntl.ioctl(s.fileno(),
+ SIOCGIFADDR,
+ struct.pack('256s', ifname))[20:24])
+
+
+def get_mac_address(ifname):
+ import fcntl
+ ifname = ifname[:15]
+ if PY3:
+ ifname = bytes(ifname, 'ascii')
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ with contextlib.closing(s):
+ info = fcntl.ioctl(
+ s.fileno(), SIOCGIFHWADDR, struct.pack('256s', ifname))
+ if PY3:
+ def ord(x):
+ return x
+ else:
+ import __builtin__
+ ord = __builtin__.ord
+ return ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1]
+
+
+def free_swap():
+ """Parse 'free' cmd and return swap memory's s total, used and free
+ values.
+ """
+ out = sh('free -b')
+ lines = out.split('\n')
+ for line in lines:
+ if line.startswith('Swap'):
+ _, total, used, free = line.split()
+ nt = collections.namedtuple('free', 'total used free')
+ return nt(int(total), int(used), int(free))
+ raise ValueError(
+ "can't find 'Swap' in 'free' output:\n%s" % '\n'.join(lines))
+
+
+def free_physmem():
+ """Parse 'free' cmd and return physical memory's total, used
+ and free values.
+ """
+ # Note: free can have 2 different formats, invalidating 'shared'
+ # and 'cached' memory which may have different positions so we
+ # do not return them.
+ # https://github.com/giampaolo/psutil/issues/538#issuecomment-57059946
+ out = sh('free -b')
+ lines = out.split('\n')
+ for line in lines:
+ if line.startswith('Mem'):
+ total, used, free, shared = \
+ [int(x) for x in line.split()[1:5]]
+ nt = collections.namedtuple(
+ 'free', 'total used free shared output')
+ return nt(total, used, free, shared, out)
+ raise ValueError(
+ "can't find 'Mem' in 'free' output:\n%s" % '\n'.join(lines))
+
+
+def vmstat(stat):
+ out = sh("vmstat -s")
+ for line in out.split("\n"):
+ line = line.strip()
+ if stat in line:
+ return int(line.split(' ')[0])
+ raise ValueError("can't find %r in 'vmstat' output" % stat)
+
+
+def get_free_version_info():
+ out = sh("free -V").strip()
+ return tuple(map(int, out.split()[-1].split('.')))
+
+
+# =====================================================================
+# --- system virtual memory
+# =====================================================================
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestSystemVirtualMemory(unittest.TestCase):
+
+ def test_total(self):
+ # free_value = free_physmem().total
+ # psutil_value = psutil.virtual_memory().total
+ # self.assertEqual(free_value, psutil_value)
+ vmstat_value = vmstat('total memory') * 1024
+ psutil_value = psutil.virtual_memory().total
+ self.assertAlmostEqual(vmstat_value, psutil_value)
+
+ # Older versions of procps used slab memory to calculate used memory.
+ # This got changed in:
+ # https://gitlab.com/procps-ng/procps/commit/
+ # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e
+ @unittest.skipIf(LINUX and get_free_version_info() < (3, 3, 12),
+ "old free version")
+ @retry_before_failing()
+ def test_used(self):
+ free = free_physmem()
+ free_value = free.used
+ psutil_value = psutil.virtual_memory().used
+ self.assertAlmostEqual(
+ free_value, psutil_value, delta=MEMORY_TOLERANCE,
+ msg='%s %s \n%s' % (free_value, psutil_value, free.output))
+
+ @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
+ @retry_before_failing()
+ def test_free(self):
+ # _, _, free_value, _ = free_physmem()
+ # psutil_value = psutil.virtual_memory().free
+ # self.assertAlmostEqual(
+ # free_value, psutil_value, delta=MEMORY_TOLERANCE)
+ vmstat_value = vmstat('free memory') * 1024
+ psutil_value = psutil.virtual_memory().free
+ self.assertAlmostEqual(
+ vmstat_value, psutil_value, delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_buffers(self):
+ vmstat_value = vmstat('buffer memory') * 1024
+ psutil_value = psutil.virtual_memory().buffers
+ self.assertAlmostEqual(
+ vmstat_value, psutil_value, delta=MEMORY_TOLERANCE)
+
+ # https://travis-ci.org/giampaolo/psutil/jobs/226719664
+ @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
+ @retry_before_failing()
+ def test_active(self):
+ vmstat_value = vmstat('active memory') * 1024
+ psutil_value = psutil.virtual_memory().active
+ self.assertAlmostEqual(
+ vmstat_value, psutil_value, delta=MEMORY_TOLERANCE)
+
+ # https://travis-ci.org/giampaolo/psutil/jobs/227242952
+ @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
+ @retry_before_failing()
+ def test_inactive(self):
+ vmstat_value = vmstat('inactive memory') * 1024
+ psutil_value = psutil.virtual_memory().inactive
+ self.assertAlmostEqual(
+ vmstat_value, psutil_value, delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_shared(self):
+ free = free_physmem()
+ free_value = free.shared
+ if free_value == 0:
+ raise unittest.SkipTest("free does not support 'shared' column")
+ psutil_value = psutil.virtual_memory().shared
+ self.assertAlmostEqual(
+ free_value, psutil_value, delta=MEMORY_TOLERANCE,
+ msg='%s %s \n%s' % (free_value, psutil_value, free.output))
+
+ @retry_before_failing()
+ def test_available(self):
+ # "free" output format has changed at some point:
+ # https://github.com/giampaolo/psutil/issues/538#issuecomment-147192098
+ out = sh("free -b")
+ lines = out.split('\n')
+ if 'available' not in lines[0]:
+ raise unittest.SkipTest("free does not support 'available' column")
+ else:
+ free_value = int(lines[1].split()[-1])
+ psutil_value = psutil.virtual_memory().available
+ self.assertAlmostEqual(
+ free_value, psutil_value, delta=MEMORY_TOLERANCE,
+ msg='%s %s \n%s' % (free_value, psutil_value, out))
+
+ def test_warnings_on_misses(self):
+ # Emulate a case where /proc/meminfo provides few info.
+ # psutil is supposed to set the missing fields to 0 and
+ # raise a warning.
+ def open_mock(name, *args, **kwargs):
+ if name == '/proc/meminfo':
+ return io.BytesIO(textwrap.dedent("""\
+ Active(anon): 6145416 kB
+ Active(file): 2950064 kB
+ Inactive(anon): 574764 kB
+ Inactive(file): 1567648 kB
+ MemAvailable: -1 kB
+ MemFree: 2057400 kB
+ MemTotal: 16325648 kB
+ SReclaimable: 346648 kB
+ """).encode())
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, create=True, side_effect=open_mock) as m:
+ with warnings.catch_warnings(record=True) as ws:
+ warnings.simplefilter("always")
+ ret = psutil.virtual_memory()
+ assert m.called
+ self.assertEqual(len(ws), 1)
+ w = ws[0]
+ assert w.filename.endswith('psutil/_pslinux.py')
+ self.assertIn(
+ "memory stats couldn't be determined", str(w.message))
+ self.assertIn("cached", str(w.message))
+ self.assertIn("shared", str(w.message))
+ self.assertIn("active", str(w.message))
+ self.assertIn("inactive", str(w.message))
+ self.assertIn("buffers", str(w.message))
+ self.assertIn("available", str(w.message))
+ self.assertEqual(ret.cached, 0)
+ self.assertEqual(ret.active, 0)
+ self.assertEqual(ret.inactive, 0)
+ self.assertEqual(ret.shared, 0)
+ self.assertEqual(ret.buffers, 0)
+ self.assertEqual(ret.available, 0)
+
+ def test_avail_old_percent(self):
+ # Make sure that our calculation of avail mem for old kernels
+ # is off by max 10%.
+ from psutil._pslinux import calculate_avail_vmem
+ from psutil._pslinux import open_binary
+
+ mems = {}
+ with open_binary('/proc/meminfo') as f:
+ for line in f:
+ fields = line.split()
+ mems[fields[0]] = int(fields[1]) * 1024
+
+ a = calculate_avail_vmem(mems)
+ if b'MemAvailable:' in mems:
+ b = mems[b'MemAvailable:']
+ diff_percent = abs(a - b) / a * 100
+ self.assertLess(diff_percent, 10)
+
+ def test_avail_old_comes_from_kernel(self):
+ # Make sure "MemAvailable:" coluimn is used instead of relying
+ # on our internal algorithm to calculate avail mem.
+ def open_mock(name, *args, **kwargs):
+ if name == "/proc/meminfo":
+ return io.BytesIO(textwrap.dedent("""\
+ Active: 9444728 kB
+ Active(anon): 6145416 kB
+ Active(file): 2950064 kB
+ Buffers: 287952 kB
+ Cached: 4818144 kB
+ Inactive(file): 1578132 kB
+ Inactive(anon): 574764 kB
+ Inactive(file): 1567648 kB
+ MemAvailable: 6574984 kB
+ MemFree: 2057400 kB
+ MemTotal: 16325648 kB
+ Shmem: 577588 kB
+ SReclaimable: 346648 kB
+ """).encode())
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, create=True, side_effect=open_mock) as m:
+ with warnings.catch_warnings(record=True) as ws:
+ ret = psutil.virtual_memory()
+ assert m.called
+ self.assertEqual(ret.available, 6574984 * 1024)
+ w = ws[0]
+ self.assertIn(
+ "inactive memory stats couldn't be determined", str(w.message))
+
+ def test_avail_old_missing_fields(self):
+ # Remove Active(file), Inactive(file) and SReclaimable
+ # from /proc/meminfo and make sure the fallback is used
+ # (free + cached),
+ def open_mock(name, *args, **kwargs):
+ if name == "/proc/meminfo":
+ return io.BytesIO(textwrap.dedent("""\
+ Active: 9444728 kB
+ Active(anon): 6145416 kB
+ Buffers: 287952 kB
+ Cached: 4818144 kB
+ Inactive(file): 1578132 kB
+ Inactive(anon): 574764 kB
+ MemFree: 2057400 kB
+ MemTotal: 16325648 kB
+ Shmem: 577588 kB
+ """).encode())
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, create=True, side_effect=open_mock) as m:
+ with warnings.catch_warnings(record=True) as ws:
+ ret = psutil.virtual_memory()
+ assert m.called
+ self.assertEqual(ret.available, 2057400 * 1024 + 4818144 * 1024)
+ w = ws[0]
+ self.assertIn(
+ "inactive memory stats couldn't be determined", str(w.message))
+
+ def test_avail_old_missing_zoneinfo(self):
+ # Remove /proc/zoneinfo file. Make sure fallback is used
+ # (free + cached).
+ def open_mock(name, *args, **kwargs):
+ if name == "/proc/meminfo":
+ return io.BytesIO(textwrap.dedent("""\
+ Active: 9444728 kB
+ Active(anon): 6145416 kB
+ Active(file): 2950064 kB
+ Buffers: 287952 kB
+ Cached: 4818144 kB
+ Inactive(file): 1578132 kB
+ Inactive(anon): 574764 kB
+ Inactive(file): 1567648 kB
+ MemFree: 2057400 kB
+ MemTotal: 16325648 kB
+ Shmem: 577588 kB
+ SReclaimable: 346648 kB
+ """).encode())
+ elif name == "/proc/zoneinfo":
+ raise IOError(errno.ENOENT, 'no such file or directory')
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, create=True, side_effect=open_mock) as m:
+ with warnings.catch_warnings(record=True) as ws:
+ ret = psutil.virtual_memory()
+ assert m.called
+ self.assertEqual(ret.available, 2057400 * 1024 + 4818144 * 1024)
+ w = ws[0]
+ self.assertIn(
+ "inactive memory stats couldn't be determined", str(w.message))
+
+
+# =====================================================================
+# --- system swap memory
+# =====================================================================
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestSystemSwapMemory(unittest.TestCase):
+
+ @staticmethod
+ def meminfo_has_swap_info():
+ """Return True if /proc/meminfo provides swap metrics."""
+ with open("/proc/meminfo") as f:
+ data = f.read()
+ return 'SwapTotal:' in data and 'SwapFree:' in data
+
+ def test_total(self):
+ free_value = free_swap().total
+ psutil_value = psutil.swap_memory().total
+ return self.assertAlmostEqual(
+ free_value, psutil_value, delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_used(self):
+ free_value = free_swap().used
+ psutil_value = psutil.swap_memory().used
+ return self.assertAlmostEqual(
+ free_value, psutil_value, delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_free(self):
+ free_value = free_swap().free
+ psutil_value = psutil.swap_memory().free
+ return self.assertAlmostEqual(
+ free_value, psutil_value, delta=MEMORY_TOLERANCE)
+
+ def test_missing_sin_sout(self):
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ with warnings.catch_warnings(record=True) as ws:
+ warnings.simplefilter("always")
+ ret = psutil.swap_memory()
+ assert m.called
+ self.assertEqual(len(ws), 1)
+ w = ws[0]
+ assert 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_no_vmstat_mocked(self):
+ # see https://github.com/giampaolo/psutil/issues/722
+ def open_mock(name, *args, **kwargs):
+ if name == "/proc/vmstat":
+ raise IOError(errno.ENOENT, 'no such file or directory')
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, create=True, side_effect=open_mock) as m:
+ with warnings.catch_warnings(record=True) as ws:
+ warnings.simplefilter("always")
+ ret = psutil.swap_memory()
+ assert m.called
+ self.assertEqual(len(ws), 1)
+ w = ws[0]
+ assert w.filename.endswith('psutil/_pslinux.py')
+ self.assertIn(
+ "'sin' and 'sout' swap memory stats couldn't "
+ "be determined and were set to 0",
+ str(w.message))
+ self.assertEqual(ret.sin, 0)
+ self.assertEqual(ret.sout, 0)
+
+ def test_meminfo_against_sysinfo(self):
+ # Make sure the content of /proc/meminfo about swap memory
+ # matches sysinfo() syscall, see:
+ # https://github.com/giampaolo/psutil/issues/1015
+ if not self.meminfo_has_swap_info():
+ return unittest.skip("/proc/meminfo has no swap metrics")
+ with mock.patch('psutil._pslinux.cext.linux_sysinfo') as m:
+ swap = psutil.swap_memory()
+ assert not m.called
+ import psutil._psutil_linux as cext
+ _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo()
+ total *= unit_multiplier
+ free *= unit_multiplier
+ self.assertEqual(swap.total, total)
+ self.assertEqual(swap.free, free)
+
+ def test_emulate_meminfo_has_no_metrics(self):
+ # Emulate a case where /proc/meminfo provides no swap metrics
+ # in which case sysinfo() syscall is supposed to be used
+ # as a fallback.
+ def open_mock(name, *args, **kwargs):
+ if name == "/proc/meminfo":
+ return io.BytesIO(b"")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, create=True, side_effect=open_mock) as m:
+ psutil.swap_memory()
+ assert m.called
+
+
+# =====================================================================
+# --- system CPU
+# =====================================================================
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestSystemCPU(unittest.TestCase):
+
+ @unittest.skipIf(TRAVIS, "unknown failure on travis")
+ def test_cpu_times(self):
+ fields = psutil.cpu_times()._fields
+ kernel_ver = re.findall(r'\d+\.\d+\.\d+', os.uname()[2])[0]
+ kernel_ver_info = tuple(map(int, kernel_ver.split('.')))
+ if kernel_ver_info >= (2, 6, 11):
+ self.assertIn('steal', fields)
+ else:
+ self.assertNotIn('steal', fields)
+ if kernel_ver_info >= (2, 6, 24):
+ self.assertIn('guest', fields)
+ else:
+ self.assertNotIn('guest', fields)
+ if kernel_ver_info >= (3, 2, 0):
+ self.assertIn('guest_nice', fields)
+ else:
+ self.assertNotIn('guest_nice', fields)
+
+ @unittest.skipIf(not os.path.exists("/sys/devices/system/cpu/online"),
+ "/sys/devices/system/cpu/online does not exist")
+ def test_cpu_count_logical_w_sysdev_cpu_online(self):
+ with open("/sys/devices/system/cpu/online") as f:
+ value = f.read().strip()
+ if "-" in str(value):
+ value = int(value.split('-')[1]) + 1
+ self.assertEqual(psutil.cpu_count(), value)
+
+ @unittest.skipIf(not os.path.exists("/sys/devices/system/cpu"),
+ "/sys/devices/system/cpu does not exist")
+ def test_cpu_count_logical_w_sysdev_cpu_num(self):
+ ls = os.listdir("/sys/devices/system/cpu")
+ count = len([x for x in ls if re.search(r"cpu\d+$", x) is not None])
+ self.assertEqual(psutil.cpu_count(), count)
+
+ @unittest.skipIf(not 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.skipIf(not 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)
+
+ def test_cpu_count_logical_mocked(self):
+ import psutil._pslinux
+ original = psutil._pslinux.cpu_count_logical()
+ # Here we want to mock os.sysconf("SC_NPROCESSORS_ONLN") in
+ # order to cause the parsing of /proc/cpuinfo and /proc/stat.
+ with mock.patch(
+ 'psutil._pslinux.os.sysconf', side_effect=ValueError) as m:
+ self.assertEqual(psutil._pslinux.cpu_count_logical(), original)
+ assert m.called
+
+ # Let's have open() return emtpy data and make sure None is
+ # returned ('cause we mimick os.cpu_count()).
+ with mock.patch('psutil._pslinux.open', create=True) as m:
+ self.assertIsNone(psutil._pslinux.cpu_count_logical())
+ self.assertEqual(m.call_count, 2)
+ # /proc/stat should be the last one
+ self.assertEqual(m.call_args[0][0], '/proc/stat')
+
+ # Let's push this a bit further and make sure /proc/cpuinfo
+ # parsing works as expected.
+ with open('/proc/cpuinfo', 'rb') as f:
+ cpuinfo_data = f.read()
+ fake_file = io.BytesIO(cpuinfo_data)
+ with mock.patch('psutil._pslinux.open',
+ return_value=fake_file, create=True) as m:
+ self.assertEqual(psutil._pslinux.cpu_count_logical(), original)
+
+ # Finally, let's make /proc/cpuinfo return meaningless data;
+ # this way we'll fall back on relying on /proc/stat
+ def open_mock(name, *args, **kwargs):
+ if name.startswith('/proc/cpuinfo'):
+ return io.BytesIO(b"")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock, create=True):
+ self.assertEqual(psutil._pslinux.cpu_count_logical(), original)
+
+ 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
+
+ @unittest.skipIf(not HAS_CPU_FREQ, "not supported")
+ def test_cpu_freq_no_result(self):
+ with mock.patch("psutil._pslinux.glob.glob", return_value=[]):
+ self.assertIsNone(psutil.cpu_freq())
+
+ @unittest.skipIf(TRAVIS, "fails on Travis")
+ @unittest.skipIf(not HAS_CPU_FREQ, "not supported")
+ def test_cpu_freq_use_second_file(self):
+ # https://github.com/giampaolo/psutil/issues/981
+ def glob_mock(pattern):
+ if pattern.startswith("/sys/devices/system/cpu/cpufreq/policy"):
+ flags.append(None)
+ return []
+ else:
+ flags.append(None)
+ return orig_glob(pattern)
+
+ flags = []
+ orig_glob = glob.glob
+ with mock.patch("psutil._pslinux.glob.glob", side_effect=glob_mock,
+ create=True):
+ assert psutil.cpu_freq()
+ self.assertEqual(len(flags), 2)
+
+ @unittest.skipIf(not HAS_CPU_FREQ, "not supported")
+ def test_cpu_freq_emulate_data(self):
+ def open_mock(name, *args, **kwargs):
+ if name.endswith('/scaling_cur_freq'):
+ return io.BytesIO(b"500000")
+ elif name.endswith('/scaling_min_freq'):
+ return io.BytesIO(b"600000")
+ elif name.endswith('/scaling_max_freq'):
+ return io.BytesIO(b"700000")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock):
+ with mock.patch(
+ 'glob.glob',
+ return_value=['/sys/devices/system/cpu/cpufreq/policy0']):
+ freq = psutil.cpu_freq()
+ self.assertEqual(freq.current, 500.0)
+ self.assertEqual(freq.min, 600.0)
+ self.assertEqual(freq.max, 700.0)
+
+ @unittest.skipIf(not HAS_CPU_FREQ, "not supported")
+ def test_cpu_freq_emulate_multi_cpu(self):
+ def open_mock(name, *args, **kwargs):
+ if name.endswith('/scaling_cur_freq'):
+ return io.BytesIO(b"100000")
+ elif name.endswith('/scaling_min_freq'):
+ return io.BytesIO(b"200000")
+ elif name.endswith('/scaling_max_freq'):
+ return io.BytesIO(b"300000")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ policies = ['/sys/devices/system/cpu/cpufreq/policy0',
+ '/sys/devices/system/cpu/cpufreq/policy1',
+ '/sys/devices/system/cpu/cpufreq/policy2']
+ with mock.patch(patch_point, side_effect=open_mock):
+ with mock.patch('glob.glob', return_value=policies):
+ freq = psutil.cpu_freq()
+ self.assertEqual(freq.current, 100.0)
+ self.assertEqual(freq.min, 200.0)
+ self.assertEqual(freq.max, 300.0)
+
+ @unittest.skipIf(TRAVIS, "fails on Travis")
+ @unittest.skipIf(not HAS_CPU_FREQ, "not supported")
+ def test_cpu_freq_no_scaling_cur_freq_file(self):
+ # See: https://github.com/giampaolo/psutil/issues/1071
+ def open_mock(name, *args, **kwargs):
+ if name.endswith('/scaling_cur_freq'):
+ raise IOError(errno.ENOENT, "")
+ elif name.endswith('/cpuinfo_cur_freq'):
+ return io.BytesIO(b"200000")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ policies = ['/sys/devices/system/cpu/cpufreq/policy0',
+ '/sys/devices/system/cpu/cpufreq/policy1',
+ '/sys/devices/system/cpu/cpufreq/policy2']
+
+ with mock.patch(patch_point, side_effect=open_mock):
+ with mock.patch('glob.glob', return_value=policies):
+ freq = psutil.cpu_freq()
+ self.assertEqual(freq.current, 200)
+
+ # Also test that NotImplementedError is raised in case no
+ # current freq file is present.
+
+ def open_mock(name, *args, **kwargs):
+ if name.endswith('/scaling_cur_freq'):
+ raise IOError(errno.ENOENT, "")
+ elif name.endswith('/cpuinfo_cur_freq'):
+ raise IOError(errno.ENOENT, "")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock):
+ with mock.patch('glob.glob', return_value=policies):
+ self.assertRaises(NotImplementedError, psutil.cpu_freq)
+
+
+# =====================================================================
+# --- system CPU stats
+# =====================================================================
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestSystemCPUStats(unittest.TestCase):
+
+ @unittest.skipIf(TRAVIS, "fails on Travis")
+ def test_ctx_switches(self):
+ vmstat_value = vmstat("context switches")
+ psutil_value = psutil.cpu_stats().ctx_switches
+ self.assertAlmostEqual(vmstat_value, psutil_value, delta=500)
+
+ @unittest.skipIf(TRAVIS, "fails on Travis")
+ def test_interrupts(self):
+ vmstat_value = vmstat("interrupts")
+ psutil_value = psutil.cpu_stats().interrupts
+ self.assertAlmostEqual(vmstat_value, psutil_value, delta=500)
+
+
+# =====================================================================
+# --- system network
+# =====================================================================
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestSystemNetwork(unittest.TestCase):
+
+ def test_net_if_addrs_ips(self):
+ for name, addrs in psutil.net_if_addrs().items():
+ for addr in addrs:
+ if addr.family == psutil.AF_LINK:
+ self.assertEqual(addr.address, get_mac_address(name))
+ elif addr.family == socket.AF_INET:
+ self.assertEqual(addr.address, get_ipv4_address(name))
+ # TODO: test for AF_INET6 family
+
+ def test_net_if_stats(self):
+ for name, stats in psutil.net_if_stats().items():
+ try:
+ out = sh("ifconfig %s" % name)
+ except RuntimeError:
+ pass
+ else:
+ # Not always reliable.
+ # self.assertEqual(stats.isup, 'RUNNING' in out, msg=out)
+ self.assertEqual(stats.mtu,
+ int(re.findall(r'(?i)MTU[: ](\d+)', out)[0]))
+
+ @retry_before_failing()
+ def test_net_io_counters(self):
+ def ifconfig(nic):
+ ret = {}
+ out = sh("ifconfig %s" % name)
+ ret['packets_recv'] = int(
+ re.findall(r'RX packets[: ](\d+)', out)[0])
+ ret['packets_sent'] = int(
+ re.findall(r'TX packets[: ](\d+)', out)[0])
+ ret['errin'] = int(re.findall(r'errors[: ](\d+)', out)[0])
+ ret['errout'] = int(re.findall(r'errors[: ](\d+)', out)[1])
+ ret['dropin'] = int(re.findall(r'dropped[: ](\d+)', out)[0])
+ ret['dropout'] = int(re.findall(r'dropped[: ](\d+)', out)[1])
+ ret['bytes_recv'] = int(
+ re.findall(r'RX (?:packets \d+ +)?bytes[: ](\d+)', out)[0])
+ ret['bytes_sent'] = int(
+ re.findall(r'TX (?:packets \d+ +)?bytes[: ](\d+)', out)[0])
+ return ret
+
+ nio = psutil.net_io_counters(pernic=True, nowrap=False)
+ for name, stats in nio.items():
+ try:
+ ifconfig_ret = ifconfig(name)
+ except RuntimeError:
+ continue
+ self.assertAlmostEqual(
+ stats.bytes_recv, ifconfig_ret['bytes_recv'], delta=1024 * 5)
+ self.assertAlmostEqual(
+ stats.bytes_sent, ifconfig_ret['bytes_sent'], delta=1024 * 5)
+ self.assertAlmostEqual(
+ stats.packets_recv, ifconfig_ret['packets_recv'], delta=1024)
+ self.assertAlmostEqual(
+ stats.packets_sent, ifconfig_ret['packets_sent'], delta=1024)
+ self.assertAlmostEqual(
+ stats.errin, ifconfig_ret['errin'], delta=10)
+ self.assertAlmostEqual(
+ stats.errout, ifconfig_ret['errout'], delta=10)
+ self.assertAlmostEqual(
+ stats.dropin, ifconfig_ret['dropin'], delta=10)
+ self.assertAlmostEqual(
+ stats.dropout, ifconfig_ret['dropout'], delta=10)
+
+ @unittest.skipIf(not which('ip'), "'ip' utility not available")
+ @unittest.skipIf(TRAVIS, "skipped on Travis")
+ def test_net_if_names(self):
+ out = sh("ip addr").strip()
+ nics = [x for x in psutil.net_if_addrs().keys() if ':' not in x]
+ found = 0
+ for line in out.split('\n'):
+ line = line.strip()
+ if re.search(r"^\d+:", line):
+ found += 1
+ name = line.split(':')[1].strip()
+ self.assertIn(name, nics)
+ self.assertEqual(len(nics), found, msg="%s\n---\n%s" % (
+ pprint.pformat(nics), out))
+
+ @mock.patch('psutil._pslinux.socket.inet_ntop', side_effect=ValueError)
+ @mock.patch('psutil._pslinux.supports_ipv6', return_value=False)
+ def test_net_connections_ipv6_unsupported(self, supports_ipv6, inet_ntop):
+ # see: https://github.com/giampaolo/psutil/issues/623
+ try:
+ s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+ self.addCleanup(s.close)
+ s.bind(("::1", 0))
+ except socket.error:
+ pass
+ psutil.net_connections(kind='inet6')
+
+ def test_net_connections_mocked(self):
+ def open_mock(name, *args, **kwargs):
+ if name == '/proc/net/unix':
+ return io.StringIO(textwrap.dedent(u"""\
+ 0: 00000003 000 000 0001 03 462170 @/tmp/dbus-Qw2hMPIU3n
+ 0: 00000003 000 000 0001 03 35010 @/tmp/dbus-tB2X8h69BQ
+ 0: 00000003 000 000 0001 03 34424 @/tmp/dbus-cHy80Y8O
+ 000000000000000000000000000000000000000000000000000000
+ """))
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ psutil.net_connections(kind='unix')
+ assert m.called
+
+
+# =====================================================================
+# --- system disk
+# =====================================================================
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestSystemDisks(unittest.TestCase):
+
+ @unittest.skipIf(not hasattr(os, 'statvfs'), "os.statvfs() not available")
+ @skip_on_not_implemented()
+ def test_disk_partitions_and_usage(self):
+ # test psutil.disk_usage() and psutil.disk_partitions()
+ # against "df -a"
+ def df(path):
+ out = sh('df -P -B 1 "%s"' % path).strip()
+ lines = out.split('\n')
+ lines.pop(0)
+ line = lines.pop(0)
+ dev, total, used, free = line.split()[:4]
+ if dev == 'none':
+ dev = ''
+ total, used, free = int(total), int(used), int(free)
+ return dev, total, used, free
+
+ for part in psutil.disk_partitions(all=False):
+ usage = psutil.disk_usage(part.mountpoint)
+ dev, total, used, free = df(part.mountpoint)
+ self.assertEqual(usage.total, total)
+ # 10 MB tollerance
+ if abs(usage.free - free) > 10 * 1024 * 1024:
+ self.fail("psutil=%s, df=%s" % (usage.free, free))
+ if abs(usage.used - used) > 10 * 1024 * 1024:
+ self.fail("psutil=%s, df=%s" % (usage.used, used))
+
+ def test_disk_partitions_mocked(self):
+ # Test that ZFS partitions are returned.
+ with open("/proc/filesystems", "r") as f:
+ data = f.read()
+ if 'zfs' in data:
+ for part in psutil.disk_partitions():
+ if part.fstype == 'zfs':
+ break
+ else:
+ self.fail("couldn't find any ZFS partition")
+ else:
+ # No ZFS partitions on this system. Let's fake one.
+ fake_file = io.StringIO(u("nodev\tzfs\n"))
+ with mock.patch('psutil._pslinux.open',
+ return_value=fake_file, create=True) as m1:
+ with mock.patch(
+ 'psutil._pslinux.cext.disk_partitions',
+ return_value=[('/dev/sdb3', '/', 'zfs', 'rw')]) as m2:
+ ret = psutil.disk_partitions()
+ assert m1.called
+ assert m2.called
+ assert ret
+ self.assertEqual(ret[0].fstype, 'zfs')
+
+ def test_disk_io_counters_kernel_2_4_mocked(self):
+ # Tests /proc/diskstats parsing format for 2.4 kernels, see:
+ # https://github.com/giampaolo/psutil/issues/767
+ def open_mock(name, *args, **kwargs):
+ if name == '/proc/partitions':
+ return io.StringIO(textwrap.dedent(u"""\
+ major minor #blocks name
+
+ 8 0 488386584 hda
+ """))
+ elif name == '/proc/diskstats':
+ return io.StringIO(
+ u(" 3 0 1 hda 2 3 4 5 6 7 8 9 10 11 12"))
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ ret = psutil.disk_io_counters(nowrap=False)
+ assert m.called
+ self.assertEqual(ret.read_count, 1)
+ self.assertEqual(ret.read_merged_count, 2)
+ self.assertEqual(ret.read_bytes, 3 * SECTOR_SIZE)
+ self.assertEqual(ret.read_time, 4)
+ self.assertEqual(ret.write_count, 5)
+ self.assertEqual(ret.write_merged_count, 6)
+ self.assertEqual(ret.write_bytes, 7 * SECTOR_SIZE)
+ self.assertEqual(ret.write_time, 8)
+ self.assertEqual(ret.busy_time, 10)
+
+ def test_disk_io_counters_kernel_2_6_full_mocked(self):
+ # Tests /proc/diskstats parsing format for 2.6 kernels,
+ # lines reporting all metrics:
+ # https://github.com/giampaolo/psutil/issues/767
+ def open_mock(name, *args, **kwargs):
+ if name == '/proc/partitions':
+ return io.StringIO(textwrap.dedent(u"""\
+ major minor #blocks name
+
+ 8 0 488386584 hda
+ """))
+ elif name == '/proc/diskstats':
+ return io.StringIO(
+ u(" 3 0 hda 1 2 3 4 5 6 7 8 9 10 11"))
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ ret = psutil.disk_io_counters(nowrap=False)
+ assert m.called
+ self.assertEqual(ret.read_count, 1)
+ self.assertEqual(ret.read_merged_count, 2)
+ self.assertEqual(ret.read_bytes, 3 * SECTOR_SIZE)
+ self.assertEqual(ret.read_time, 4)
+ self.assertEqual(ret.write_count, 5)
+ self.assertEqual(ret.write_merged_count, 6)
+ self.assertEqual(ret.write_bytes, 7 * SECTOR_SIZE)
+ self.assertEqual(ret.write_time, 8)
+ self.assertEqual(ret.busy_time, 10)
+
+ def test_disk_io_counters_kernel_2_6_limited_mocked(self):
+ # Tests /proc/diskstats parsing format for 2.6 kernels,
+ # where one line of /proc/partitions return a limited
+ # amount of metrics when it bumps into a partition
+ # (instead of a disk). See:
+ # https://github.com/giampaolo/psutil/issues/767
+ def open_mock(name, *args, **kwargs):
+ if name == '/proc/partitions':
+ return io.StringIO(textwrap.dedent(u"""\
+ major minor #blocks name
+
+ 8 0 488386584 hda
+ """))
+ elif name == '/proc/diskstats':
+ return io.StringIO(
+ u(" 3 1 hda 1 2 3 4"))
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ ret = psutil.disk_io_counters(nowrap=False)
+ assert m.called
+ self.assertEqual(ret.read_count, 1)
+ self.assertEqual(ret.read_bytes, 2 * SECTOR_SIZE)
+ self.assertEqual(ret.write_count, 3)
+ self.assertEqual(ret.write_bytes, 4 * SECTOR_SIZE)
+
+ self.assertEqual(ret.read_merged_count, 0)
+ self.assertEqual(ret.read_time, 0)
+ self.assertEqual(ret.write_merged_count, 0)
+ self.assertEqual(ret.write_time, 0)
+ self.assertEqual(ret.busy_time, 0)
+
+
+# =====================================================================
+# --- misc
+# =====================================================================
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestMisc(unittest.TestCase):
+
+ def test_boot_time(self):
+ vmstat_value = vmstat('boot time')
+ psutil_value = psutil.boot_time()
+ self.assertEqual(int(vmstat_value), int(psutil_value))
+
+ @mock.patch('psutil.traceback.print_exc')
+ def test_no_procfs_on_import(self, tb):
+ my_procfs = tempfile.mkdtemp()
+
+ with open(os.path.join(my_procfs, 'stat'), 'w') as f:
+ f.write('cpu 0 0 0 0 0 0 0 0 0 0\n')
+ f.write('cpu0 0 0 0 0 0 0 0 0 0 0\n')
+ f.write('cpu1 0 0 0 0 0 0 0 0 0 0\n')
+
+ try:
+ orig_open = open
+
+ def open_mock(name, *args, **kwargs):
+ if name.startswith('/proc'):
+ raise IOError(errno.ENOENT, 'rejecting access for test')
+ return orig_open(name, *args, **kwargs)
+
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock):
+ reload_module(psutil)
+ assert tb.called
+
+ self.assertRaises(IOError, psutil.cpu_times)
+ self.assertRaises(IOError, psutil.cpu_times, percpu=True)
+ self.assertRaises(IOError, psutil.cpu_percent)
+ self.assertRaises(IOError, psutil.cpu_percent, percpu=True)
+ self.assertRaises(IOError, psutil.cpu_times_percent)
+ self.assertRaises(
+ IOError, psutil.cpu_times_percent, percpu=True)
+
+ psutil.PROCFS_PATH = my_procfs
+
+ self.assertEqual(psutil.cpu_percent(), 0)
+ self.assertEqual(sum(psutil.cpu_times_percent()), 0)
+
+ # since we don't know the number of CPUs at import time,
+ # we awkwardly say there are none until the second call
+ per_cpu_percent = psutil.cpu_percent(percpu=True)
+ self.assertEqual(sum(per_cpu_percent), 0)
+
+ # ditto awkward length
+ per_cpu_times_percent = psutil.cpu_times_percent(percpu=True)
+ self.assertEqual(sum(map(sum, per_cpu_times_percent)), 0)
+
+ # much user, very busy
+ with open(os.path.join(my_procfs, 'stat'), 'w') as f:
+ f.write('cpu 1 0 0 0 0 0 0 0 0 0\n')
+ f.write('cpu0 1 0 0 0 0 0 0 0 0 0\n')
+ f.write('cpu1 1 0 0 0 0 0 0 0 0 0\n')
+
+ self.assertNotEqual(psutil.cpu_percent(), 0)
+ self.assertNotEqual(
+ sum(psutil.cpu_percent(percpu=True)), 0)
+ self.assertNotEqual(sum(psutil.cpu_times_percent()), 0)
+ self.assertNotEqual(
+ sum(map(sum, psutil.cpu_times_percent(percpu=True))), 0)
+ finally:
+ shutil.rmtree(my_procfs)
+ reload_module(psutil)
+
+ self.assertEqual(psutil.PROCFS_PATH, '/proc')
+
+ 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
+
+ def test_users_mocked(self):
+ # Make sure ':0' and ':0.0' (returned by C ext) are converted
+ # to 'localhost'.
+ with mock.patch('psutil._pslinux.cext.users',
+ return_value=[('giampaolo', 'pts/2', ':0',
+ 1436573184.0, True, 2)]) as m:
+ self.assertEqual(psutil.users()[0].host, 'localhost')
+ assert m.called
+ with mock.patch('psutil._pslinux.cext.users',
+ return_value=[('giampaolo', 'pts/2', ':0.0',
+ 1436573184.0, True, 2)]) as m:
+ self.assertEqual(psutil.users()[0].host, 'localhost')
+ assert m.called
+ # ...otherwise it should be returned as-is
+ with mock.patch('psutil._pslinux.cext.users',
+ return_value=[('giampaolo', 'pts/2', 'foo',
+ 1436573184.0, True, 2)]) as m:
+ self.assertEqual(psutil.users()[0].host, 'foo')
+ assert m.called
+
+ def test_procfs_path(self):
+ tdir = tempfile.mkdtemp()
+ try:
+ psutil.PROCFS_PATH = tdir
+ self.assertRaises(IOError, psutil.virtual_memory)
+ self.assertRaises(IOError, psutil.cpu_times)
+ self.assertRaises(IOError, psutil.cpu_times, percpu=True)
+ self.assertRaises(IOError, psutil.boot_time)
+ # self.assertRaises(IOError, psutil.pids)
+ self.assertRaises(IOError, psutil.net_connections)
+ self.assertRaises(IOError, psutil.net_io_counters)
+ self.assertRaises(IOError, psutil.net_if_stats)
+ self.assertRaises(IOError, psutil.disk_io_counters)
+ self.assertRaises(IOError, psutil.disk_partitions)
+ self.assertRaises(psutil.NoSuchProcess, psutil.Process)
+ finally:
+ psutil.PROCFS_PATH = "/proc"
+ os.rmdir(tdir)
+
+ def test_sector_size_mock(self):
+ # Test SECTOR_SIZE fallback in case 'hw_sector_size' file
+ # does not exist.
+ def open_mock(name, *args, **kwargs):
+ if PY3 and isinstance(name, bytes):
+ name = name.decode()
+ if "hw_sector_size" in name:
+ flag.append(None)
+ raise IOError(errno.ENOENT, '')
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ flag = []
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock):
+ psutil.disk_io_counters()
+ assert flag
+
+ def test_issue_687(self):
+ # In case of thread ID:
+ # - pid_exists() is supposed to return False
+ # - Process(tid) is supposed to work
+ # - pids() should not return the TID
+ # See: https://github.com/giampaolo/psutil/issues/687
+ t = ThreadTask()
+ t.start()
+ try:
+ p = psutil.Process()
+ tid = p.threads()[1].id
+ assert not psutil.pid_exists(tid), tid
+ pt = psutil.Process(tid)
+ pt.as_dict()
+ self.assertNotIn(tid, psutil.pids())
+ finally:
+ t.stop()
+
+ def test_pid_exists_no_proc_status(self):
+ # Internally pid_exists relies on /proc/{pid}/status.
+ # Emulate a case where this file is empty in which case
+ # psutil is supposed to fall back on using pids().
+ def open_mock(name, *args, **kwargs):
+ if name == "/proc/%s/status" % os.getpid():
+ return io.StringIO(u(""))
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock):
+ assert psutil.pid_exists(os.getpid())
+
+
+# =====================================================================
+# --- sensors
+# =====================================================================
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+@unittest.skipIf(not HAS_BATTERY, "no battery")
+class TestSensorsBattery(unittest.TestCase):
+
+ @unittest.skipIf(not which("acpi"), "acpi utility not available")
+ def test_percent(self):
+ out = sh("acpi -b")
+ acpi_value = int(out.split(",")[1].strip().replace('%', ''))
+ psutil_value = psutil.sensors_battery().percent
+ self.assertAlmostEqual(acpi_value, psutil_value, delta=1)
+
+ @unittest.skipIf(not which("acpi"), "acpi utility not available")
+ def test_power_plugged(self):
+ out = sh("acpi -b")
+ if 'unknown' in out.lower():
+ return unittest.skip("acpi output not reliable")
+ if 'discharging at zero rate' in out:
+ plugged = True
+ else:
+ plugged = "Charging" in out.split('\n')[0]
+ self.assertEqual(psutil.sensors_battery().power_plugged, plugged)
+
+ def test_emulate_power_plugged(self):
+ # Pretend the AC power cable is connected.
+ def open_mock(name, *args, **kwargs):
+ if name.endswith("AC0/online") or name.endswith("AC/online"):
+ return io.BytesIO(b"1")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ self.assertEqual(psutil.sensors_battery().power_plugged, True)
+ self.assertEqual(
+ psutil.sensors_battery().secsleft, psutil.POWER_TIME_UNLIMITED)
+ assert m.called
+
+ def test_emulate_power_plugged_2(self):
+ # Same as above but pretend /AC0/online does not exist in which
+ # case code relies on /status file.
+ def open_mock(name, *args, **kwargs):
+ if name.endswith("AC0/online") or name.endswith("AC/online"):
+ raise IOError(errno.ENOENT, "")
+ elif name.endswith("/status"):
+ return io.StringIO(u("charging"))
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ self.assertEqual(psutil.sensors_battery().power_plugged, True)
+ assert m.called
+
+ def test_emulate_power_not_plugged(self):
+ # Pretend the AC power cable is not connected.
+ def open_mock(name, *args, **kwargs):
+ if name.endswith("AC0/online") or name.endswith("AC/online"):
+ return io.BytesIO(b"0")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ self.assertEqual(psutil.sensors_battery().power_plugged, False)
+ assert m.called
+
+ def test_emulate_power_not_plugged_2(self):
+ # Same as above but pretend /AC0/online does not exist in which
+ # case code relies on /status file.
+ def open_mock(name, *args, **kwargs):
+ if name.endswith("AC0/online") or name.endswith("AC/online"):
+ raise IOError(errno.ENOENT, "")
+ elif name.endswith("/status"):
+ return io.StringIO(u("discharging"))
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ self.assertEqual(psutil.sensors_battery().power_plugged, False)
+ assert m.called
+
+ def test_emulate_power_undetermined(self):
+ # Pretend we can't know whether the AC power cable not
+ # connected (assert fallback to False).
+ def open_mock(name, *args, **kwargs):
+ if name.startswith("/sys/class/power_supply/AC0/online") or \
+ name.startswith("/sys/class/power_supply/AC/online"):
+ raise IOError(errno.ENOENT, "")
+ elif name.startswith("/sys/class/power_supply/BAT0/status"):
+ return io.BytesIO(b"???")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ self.assertIsNone(psutil.sensors_battery().power_plugged)
+ assert m.called
+
+ def test_emulate_no_base_files(self):
+ # Emulate a case where base metrics files are not present,
+ # in which case we're supposed to get None.
+ def open_mock(name, *args, **kwargs):
+ if name.startswith("/sys/class/power_supply/BAT0/energy_now") or \
+ name.startswith("/sys/class/power_supply/BAT0/charge_now"):
+ raise IOError(errno.ENOENT, "")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ self.assertIsNone(psutil.sensors_battery())
+ assert m.called
+
+ def test_emulate_energy_full_0(self):
+ # Emulate a case where energy_full files returns 0.
+ def open_mock(name, *args, **kwargs):
+ if name.startswith("/sys/class/power_supply/BAT0/energy_full"):
+ return io.BytesIO(b"0")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ self.assertEqual(psutil.sensors_battery().percent, 0)
+ assert m.called
+
+ def test_emulate_energy_full_not_avail(self):
+ # Emulate a case where energy_full file does not exist.
+ # Expected fallback on /capacity.
+ def open_mock(name, *args, **kwargs):
+ energy_full = "/sys/class/power_supply/BAT0/energy_full"
+ charge_full = "/sys/class/power_supply/BAT0/charge_full"
+ if name.startswith(energy_full) or name.startswith(charge_full):
+ raise IOError(errno.ENOENT, "")
+ elif name.startswith("/sys/class/power_supply/BAT0/capacity"):
+ return io.BytesIO(b"88")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ self.assertEqual(psutil.sensors_battery().percent, 88)
+ assert m.called
+
+ def test_emulate_no_ac0_online(self):
+ # Emulate a case where /AC0/online file does not exist.
+ def path_exists_mock(name):
+ if name.startswith("/sys/class/power_supply/AC0/online"):
+ return False
+ else:
+ return orig_path_exists(name)
+
+ orig_path_exists = os.path.exists
+ with mock.patch("psutil._pslinux.os.path.exists",
+ side_effect=path_exists_mock) as m:
+ psutil.sensors_battery()
+ assert m.called
+
+ def test_emulate_no_power(self):
+ # Emulate a case where /AC0/online file nor /BAT0/status exist.
+ def open_mock(name, *args, **kwargs):
+ if name.startswith("/sys/class/power_supply/AC/online") or \
+ name.startswith("/sys/class/power_supply/AC0/online") or \
+ name.startswith("/sys/class/power_supply/BAT0/status"):
+ raise IOError(errno.ENOENT, "")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ self.assertIsNone(psutil.sensors_battery().power_plugged)
+ assert m.called
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestSensorsTemperatures(unittest.TestCase):
+
+ @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
+ def test_emulate_eio_error(self):
+ def open_mock(name, *args, **kwargs):
+ if name.endswith("_input"):
+ raise OSError(errno.EIO, "")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ with warnings.catch_warnings(record=True) as ws:
+ self.assertEqual(psutil.sensors_temperatures(), {})
+ assert m.called
+ self.assertIn("ignoring", str(ws[0].message))
+
+ def test_emulate_data(self):
+ def open_mock(name, *args, **kwargs):
+ if name.endswith('/name'):
+ return io.StringIO(u("name"))
+ elif name.endswith('/temp1_label'):
+ return io.StringIO(u("label"))
+ elif name.endswith('/temp1_input'):
+ return io.BytesIO(b"30000")
+ elif name.endswith('/temp1_max'):
+ return io.BytesIO(b"40000")
+ elif name.endswith('/temp1_crit'):
+ return io.BytesIO(b"50000")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock):
+ with mock.patch('glob.glob',
+ return_value=['/sys/class/hwmon/hwmon0/temp1']):
+ temp = psutil.sensors_temperatures()['name'][0]
+ self.assertEqual(temp.label, 'label')
+ self.assertEqual(temp.current, 30.0)
+ self.assertEqual(temp.high, 40.0)
+ self.assertEqual(temp.critical, 50.0)
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestSensorsFans(unittest.TestCase):
+
+ def test_emulate_data(self):
+ def open_mock(name, *args, **kwargs):
+ if name.endswith('/name'):
+ return io.StringIO(u("name"))
+ elif name.endswith('/fan1_label'):
+ return io.StringIO(u("label"))
+ elif name.endswith('/fan1_input'):
+ return io.StringIO(u("2000"))
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock):
+ with mock.patch('glob.glob',
+ return_value=['/sys/class/hwmon/hwmon2/fan1']):
+ fan = psutil.sensors_fans()['name'][0]
+ self.assertEqual(fan.label, 'label')
+ self.assertEqual(fan.current, 2000)
+
+
+# =====================================================================
+# --- test process
+# =====================================================================
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestProcess(unittest.TestCase):
+
+ def setUp(self):
+ safe_rmpath(TESTFN)
+
+ tearDown = setUp
+
+ def test_memory_full_info(self):
+ src = textwrap.dedent("""
+ import time
+ with open("%s", "w") as f:
+ time.sleep(10)
+ """ % TESTFN)
+ sproc = pyrun(src)
+ self.addCleanup(reap_children)
+ call_until(lambda: os.listdir('.'), "'%s' not in ret" % TESTFN)
+ p = psutil.Process(sproc.pid)
+ time.sleep(.1)
+ mem = p.memory_full_info()
+ maps = p.memory_maps(grouped=False)
+ self.assertAlmostEqual(
+ mem.uss, sum([x.private_dirty + x.private_clean for x in maps]),
+ delta=4096)
+ self.assertAlmostEqual(
+ mem.pss, sum([x.pss for x in maps]), delta=4096)
+ self.assertAlmostEqual(
+ mem.swap, sum([x.swap for x in maps]), delta=4096)
+
+ # On PYPY file descriptors are not closed fast enough.
+ @unittest.skipIf(PYPY, "unreliable on PYPY")
+ def test_open_files_mode(self):
+ def get_test_file():
+ p = psutil.Process()
+ giveup_at = time.time() + 2
+ while True:
+ for file in p.open_files():
+ if file.path == os.path.abspath(TESTFN):
+ return file
+ elif time.time() > giveup_at:
+ break
+ raise RuntimeError("timeout looking for test file")
+
+ #
+ with open(TESTFN, "w"):
+ self.assertEqual(get_test_file().mode, "w")
+ with open(TESTFN, "r"):
+ self.assertEqual(get_test_file().mode, "r")
+ with open(TESTFN, "a"):
+ self.assertEqual(get_test_file().mode, "a")
+ #
+ with open(TESTFN, "r+"):
+ self.assertEqual(get_test_file().mode, "r+")
+ with open(TESTFN, "w+"):
+ self.assertEqual(get_test_file().mode, "r+")
+ with open(TESTFN, "a+"):
+ self.assertEqual(get_test_file().mode, "a+")
+ # note: "x" bit is not supported
+ if PY3:
+ safe_rmpath(TESTFN)
+ with open(TESTFN, "x"):
+ self.assertEqual(get_test_file().mode, "w")
+ safe_rmpath(TESTFN)
+ with open(TESTFN, "x+"):
+ self.assertEqual(get_test_file().mode, "r+")
+
+ def test_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_open_files_fd_gone(self):
+ # Simulate a case where /proc/{pid}/fdinfo/{fd} disappears
+ # while iterating through fds.
+ # https://travis-ci.org/giampaolo/psutil/jobs/225694530
+ 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))
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point,
+ side_effect=IOError(errno.ENOENT, "")) as m:
+ files = p.open_files()
+ assert not files
+ assert m.called
+
+ # --- mocked tests
+
+ def test_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
+
+ # TODO: re-enable this test.
+ # def test_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_cmdline_mocked(self):
+ # see: https://github.com/giampaolo/psutil/issues/639
+ p = psutil.Process()
+ fake_file = io.StringIO(u('foo\x00bar\x00'))
+ with mock.patch('psutil._pslinux.open',
+ return_value=fake_file, create=True) as m:
+ self.assertEqual(p.cmdline(), ['foo', 'bar'])
+ assert m.called
+ fake_file = io.StringIO(u('foo\x00bar\x00\x00'))
+ with mock.patch('psutil._pslinux.open',
+ return_value=fake_file, create=True) as m:
+ self.assertEqual(p.cmdline(), ['foo', 'bar', ''])
+ assert m.called
+
+ def test_cmdline_spaces_mocked(self):
+ # see: https://github.com/giampaolo/psutil/issues/1179
+ p = psutil.Process()
+ fake_file = io.StringIO(u('foo bar '))
+ with mock.patch('psutil._pslinux.open',
+ return_value=fake_file, create=True) as m:
+ self.assertEqual(p.cmdline(), ['foo', 'bar'])
+ assert m.called
+ fake_file = io.StringIO(u('foo bar '))
+ with mock.patch('psutil._pslinux.open',
+ return_value=fake_file, create=True) as m:
+ self.assertEqual(p.cmdline(), ['foo', 'bar', ''])
+ assert m.called
+
+ def test_readlink_path_deleted_mocked(self):
+ with mock.patch('psutil._pslinux.os.readlink',
+ return_value='/home/foo (deleted)'):
+ self.assertEqual(psutil.Process().exe(), "/home/foo")
+ self.assertEqual(psutil.Process().cwd(), "/home/foo")
+
+ def test_threads_mocked(self):
+ # Test the case where os.listdir() returns a file (thread)
+ # which no longer exists by the time we open() it (race
+ # condition). threads() is supposed to ignore that instead
+ # of raising NSP.
+ def open_mock(name, *args, **kwargs):
+ if name.startswith('/proc/%s/task' % os.getpid()):
+ raise IOError(errno.ENOENT, "")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ ret = psutil.Process().threads()
+ assert m.called
+ self.assertEqual(ret, [])
+
+ # ...but if it bumps into something != ENOENT we want an
+ # exception.
+ def open_mock(name, *args, **kwargs):
+ if name.startswith('/proc/%s/task' % os.getpid()):
+ raise IOError(errno.EPERM, "")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ with mock.patch(patch_point, side_effect=open_mock):
+ self.assertRaises(psutil.AccessDenied, psutil.Process().threads)
+
+ def test_exe_mocked(self):
+ with mock.patch('psutil._pslinux.readlink',
+ side_effect=OSError(errno.ENOENT, "")) as m1:
+ with mock.patch('psutil.Process.cmdline',
+ side_effect=psutil.AccessDenied(0, "")) as m2:
+ # No such file error; might be raised also if /proc/pid/exe
+ # path actually exists for system processes with low pids
+ # (about 0-20). In this case psutil is supposed to return
+ # an empty string.
+ ret = psutil.Process().exe()
+ assert m1.called
+ assert m2.called
+ self.assertEqual(ret, "")
+
+ # ...but if /proc/pid no longer exist we're supposed to treat
+ # it as an alias for zombie process
+ with mock.patch('psutil._pslinux.os.path.lexists',
+ return_value=False):
+ self.assertRaises(
+ psutil.ZombieProcess, psutil.Process().exe)
+
+ def test_issue_1014(self):
+ # Emulates a case where smaps file does not exist. In this case
+ # wrap_exception decorator should not raise NoSuchProcess.
+ def open_mock(name, *args, **kwargs):
+ if name.startswith('/proc/%s/smaps' % os.getpid()):
+ raise IOError(errno.ENOENT, "")
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock) as m:
+ p = psutil.Process()
+ with self.assertRaises(IOError) as err:
+ p.memory_maps()
+ self.assertEqual(err.exception.errno, errno.ENOENT)
+ assert m.called
+
+ @unittest.skipIf(not HAS_RLIMIT, "not supported")
+ def test_rlimit_zombie(self):
+ # Emulate a case where rlimit() raises ENOSYS, which may
+ # happen in case of zombie process:
+ # https://travis-ci.org/giampaolo/psutil/jobs/51368273
+ with mock.patch("psutil._pslinux.cext.linux_prlimit",
+ side_effect=OSError(errno.ENOSYS, "")) as m:
+ p = psutil.Process()
+ p.name()
+ with self.assertRaises(psutil.ZombieProcess) as exc:
+ p.rlimit(psutil.RLIMIT_NOFILE)
+ assert m.called
+ self.assertEqual(exc.exception.pid, p.pid)
+ self.assertEqual(exc.exception.name, p.name())
+
+ def test_cwd_zombie(self):
+ with mock.patch("psutil._pslinux.os.readlink",
+ side_effect=OSError(errno.ENOENT, "")) as m:
+ p = psutil.Process()
+ p.name()
+ with self.assertRaises(psutil.ZombieProcess) as exc:
+ p.cwd()
+ assert m.called
+ self.assertEqual(exc.exception.pid, p.pid)
+ self.assertEqual(exc.exception.name, p.name())
+
+ def test_stat_file_parsing(self):
+ from psutil._pslinux import CLOCK_TICKS
+
+ def open_mock(name, *args, **kwargs):
+ if name.startswith('/proc/%s/stat' % os.getpid()):
+ args = [
+ "0", # pid
+ "(cat)", # name
+ "Z", # status
+ "1", # ppid
+ "0", # pgrp
+ "0", # session
+ "0", # tty
+ "0", # tpgid
+ "0", # flags
+ "0", # minflt
+ "0", # cminflt
+ "0", # majflt
+ "0", # cmajflt
+ "2", # utime
+ "3", # stime
+ "4", # cutime
+ "5", # cstime
+ "0", # priority
+ "0", # nice
+ "0", # num_threads
+ "0", # itrealvalue
+ "6", # starttime
+ "0", # vsize
+ "0", # rss
+ "0", # rsslim
+ "0", # startcode
+ "0", # endcode
+ "0", # startstack
+ "0", # kstkesp
+ "0", # kstkeip
+ "0", # signal
+ "0", # blocked
+ "0", # sigignore
+ "0", # sigcatch
+ "0", # wchan
+ "0", # nswap
+ "0", # cnswap
+ "0", # exit_signal
+ "6", # processor
+ ]
+ return io.BytesIO(" ".join(args).encode())
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock):
+ p = psutil.Process()
+ self.assertEqual(p.name(), 'cat')
+ self.assertEqual(p.status(), psutil.STATUS_ZOMBIE)
+ self.assertEqual(p.ppid(), 1)
+ self.assertEqual(
+ p.create_time(), 6 / CLOCK_TICKS + psutil.boot_time())
+ cpu = p.cpu_times()
+ self.assertEqual(cpu.user, 2 / CLOCK_TICKS)
+ self.assertEqual(cpu.system, 3 / CLOCK_TICKS)
+ self.assertEqual(cpu.children_user, 4 / CLOCK_TICKS)
+ self.assertEqual(cpu.children_system, 5 / CLOCK_TICKS)
+ self.assertEqual(p.cpu_num(), 6)
+
+ def test_status_file_parsing(self):
+ def open_mock(name, *args, **kwargs):
+ if name.startswith('/proc/%s/status' % os.getpid()):
+ return io.BytesIO(textwrap.dedent("""\
+ Uid:\t1000\t1001\t1002\t1003
+ Gid:\t1004\t1005\t1006\t1007
+ Threads:\t66
+ Cpus_allowed:\tf
+ Cpus_allowed_list:\t0-7
+ voluntary_ctxt_switches:\t12
+ nonvoluntary_ctxt_switches:\t13""").encode())
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch(patch_point, side_effect=open_mock):
+ p = psutil.Process()
+ self.assertEqual(p.num_ctx_switches().voluntary, 12)
+ self.assertEqual(p.num_ctx_switches().involuntary, 13)
+ self.assertEqual(p.num_threads(), 66)
+ uids = p.uids()
+ self.assertEqual(uids.real, 1000)
+ self.assertEqual(uids.effective, 1001)
+ self.assertEqual(uids.saved, 1002)
+ gids = p.gids()
+ self.assertEqual(gids.real, 1004)
+ self.assertEqual(gids.effective, 1005)
+ self.assertEqual(gids.saved, 1006)
+ self.assertEqual(p._proc._get_eligible_cpus(), list(range(0, 8)))
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestProcessAgainstStatus(unittest.TestCase):
+ """/proc/pid/stat and /proc/pid/status have many values in common.
+ Whenever possible, psutil uses /proc/pid/stat (it's faster).
+ For all those cases we check that the value found in
+ /proc/pid/stat (by psutil) matches the one found in
+ /proc/pid/status.
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ cls.proc = psutil.Process()
+
+ def read_status_file(self, linestart):
+ with psutil._psplatform.open_text(
+ '/proc/%s/status' % self.proc.pid) as f:
+ for line in f:
+ line = line.strip()
+ if line.startswith(linestart):
+ value = line.partition('\t')[2]
+ try:
+ return int(value)
+ except ValueError:
+ return value
+ raise ValueError("can't find %r" % linestart)
+
+ def test_name(self):
+ value = self.read_status_file("Name:")
+ self.assertEqual(self.proc.name(), value)
+
+ def test_status(self):
+ value = self.read_status_file("State:")
+ value = value[value.find('(') + 1:value.rfind(')')]
+ value = value.replace(' ', '-')
+ self.assertEqual(self.proc.status(), value)
+
+ def test_ppid(self):
+ value = self.read_status_file("PPid:")
+ self.assertEqual(self.proc.ppid(), value)
+
+ def test_num_threads(self):
+ value = self.read_status_file("Threads:")
+ self.assertEqual(self.proc.num_threads(), value)
+
+ def test_uids(self):
+ value = self.read_status_file("Uid:")
+ value = tuple(map(int, value.split()[1:4]))
+ self.assertEqual(self.proc.uids(), value)
+
+ def test_gids(self):
+ value = self.read_status_file("Gid:")
+ value = tuple(map(int, value.split()[1:4]))
+ self.assertEqual(self.proc.gids(), value)
+
+ @retry_before_failing()
+ def test_num_ctx_switches(self):
+ value = self.read_status_file("voluntary_ctxt_switches:")
+ self.assertEqual(self.proc.num_ctx_switches().voluntary, value)
+ value = self.read_status_file("nonvoluntary_ctxt_switches:")
+ self.assertEqual(self.proc.num_ctx_switches().involuntary, value)
+
+ def test_cpu_affinity(self):
+ value = self.read_status_file("Cpus_allowed_list:")
+ if '-' in str(value):
+ min_, max_ = map(int, value.split('-'))
+ self.assertEqual(
+ self.proc.cpu_affinity(), list(range(min_, max_ + 1)))
+
+ def test_cpu_affinity_eligible_cpus(self):
+ value = self.read_status_file("Cpus_allowed_list:")
+ with mock.patch("psutil._pslinux.per_cpu_times") as m:
+ self.proc._proc._get_eligible_cpus()
+ if '-' in str(value):
+ assert not m.called
+ else:
+ assert m.called
+
+
+# =====================================================================
+# --- test utils
+# =====================================================================
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestUtils(unittest.TestCase):
+
+ def test_open_text(self):
+ with psutil._psplatform.open_text(__file__) as f:
+ self.assertEqual(f.mode, 'rt')
+
+ def test_open_binary(self):
+ with psutil._psplatform.open_binary(__file__) as f:
+ self.assertEqual(f.mode, 'rb')
+
+ def test_readlink(self):
+ with mock.patch("os.readlink", return_value="foo (deleted)") as m:
+ self.assertEqual(psutil._psplatform.readlink("bar"), "foo")
+ assert m.called
+
+ def test_cat(self):
+ fname = os.path.abspath(TESTFN)
+ with open(fname, "wt") as f:
+ f.write("foo ")
+ self.assertEqual(psutil._psplatform.cat(TESTFN, binary=False), "foo")
+ self.assertEqual(psutil._psplatform.cat(TESTFN, binary=True), b"foo")
+ self.assertEqual(
+ psutil._psplatform.cat(TESTFN + '??', fallback="bar"), "bar")
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_memory_leaks.py b/psutil/tests/test_memory_leaks.py
new file mode 100755
index 00000000..680fe780
--- /dev/null
+++ b/psutil/tests/test_memory_leaks.py
@@ -0,0 +1,599 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Tests for detecting function memory leaks (typically the ones
+implemented in C). It does so by calling a function many times and
+checking whether process memory usage keeps increasing between
+calls or over time.
+Note that this may produce false positives (especially on Windows
+for some reason).
+"""
+
+from __future__ import print_function
+import errno
+import functools
+import gc
+import os
+import sys
+import threading
+import time
+
+import psutil
+import psutil._common
+from psutil import LINUX
+from psutil import OPENBSD
+from psutil import OSX
+from psutil import POSIX
+from psutil import SUNOS
+from psutil import WINDOWS
+from psutil._compat import xrange
+from psutil.tests import create_sockets
+from psutil.tests import get_test_subprocess
+from psutil.tests import HAS_CPU_AFFINITY
+from psutil.tests import HAS_CPU_FREQ
+from psutil.tests import HAS_ENVIRON
+from psutil.tests import HAS_IONICE
+from psutil.tests import HAS_MEMORY_MAPS
+from psutil.tests import HAS_PROC_CPU_NUM
+from psutil.tests import HAS_PROC_IO_COUNTERS
+from psutil.tests import HAS_RLIMIT
+from psutil.tests import HAS_SENSORS_BATTERY
+from psutil.tests import HAS_SENSORS_FANS
+from psutil.tests import HAS_SENSORS_TEMPERATURES
+from psutil.tests import reap_children
+from psutil.tests import run_test_module_by_name
+from psutil.tests import safe_rmpath
+from psutil.tests import skip_on_access_denied
+from psutil.tests import TESTFN
+from psutil.tests import TRAVIS
+from psutil.tests import unittest
+
+
+LOOPS = 1000
+MEMORY_TOLERANCE = 4096
+RETRY_FOR = 3
+
+SKIP_PYTHON_IMPL = True if TRAVIS else False
+cext = psutil._psplatform.cext
+thisproc = psutil.Process()
+SKIP_PYTHON_IMPL = True if TRAVIS else False
+
+
+# ===================================================================
+# utils
+# ===================================================================
+
+
+def skip_if_linux():
+ return unittest.skipIf(LINUX and SKIP_PYTHON_IMPL,
+ "worthless on LINUX (pure python)")
+
+
+def bytes2human(n):
+ """
+ http://code.activestate.com/recipes/578019
+ >>> bytes2human(10000)
+ '9.8K'
+ >>> bytes2human(100001221)
+ '95.4M'
+ """
+ symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
+ prefix = {}
+ for i, s in enumerate(symbols):
+ prefix[s] = 1 << (i + 1) * 10
+ for s in reversed(symbols):
+ if n >= prefix[s]:
+ value = float(n) / prefix[s]
+ return '%.2f%s' % (value, s)
+ return "%sB" % n
+
+
+class TestMemLeak(unittest.TestCase):
+ """Base framework class which calls a function many times and
+ produces a failure if process memory usage keeps increasing
+ between calls or over time.
+ """
+ tolerance = MEMORY_TOLERANCE
+ loops = LOOPS
+ retry_for = RETRY_FOR
+
+ def setUp(self):
+ gc.collect()
+
+ def execute(self, fun, *args, **kwargs):
+ """Test a callable."""
+ def call_many_times():
+ for x in xrange(loops):
+ self._call(fun, *args, **kwargs)
+ del x
+ gc.collect()
+
+ tolerance = kwargs.pop('tolerance_', None) or self.tolerance
+ loops = kwargs.pop('loops_', None) or self.loops
+ retry_for = kwargs.pop('retry_for_', None) or self.retry_for
+
+ # warm up
+ for x in range(10):
+ self._call(fun, *args, **kwargs)
+ self.assertEqual(gc.garbage, [])
+ self.assertEqual(threading.active_count(), 1)
+ self.assertEqual(thisproc.children(), [])
+
+ # Get 2 distinct memory samples, before and after having
+ # called fun repeadetly.
+ # step 1
+ call_many_times()
+ mem1 = self._get_mem()
+ # step 2
+ call_many_times()
+ mem2 = self._get_mem()
+
+ diff1 = mem2 - mem1
+ if diff1 > tolerance:
+ # This doesn't necessarily mean we have a leak yet.
+ # At this point we assume that after having called the
+ # function so many times the memory usage is stabilized
+ # and if there are no leaks it should not increase
+ # anymore.
+ # Let's keep calling fun for 3 more seconds and fail if
+ # we notice any difference.
+ ncalls = 0
+ stop_at = time.time() + retry_for
+ while time.time() <= stop_at:
+ self._call(fun, *args, **kwargs)
+ ncalls += 1
+
+ del stop_at
+ gc.collect()
+ mem3 = self._get_mem()
+ diff2 = mem3 - mem2
+
+ if mem3 > mem2:
+ # failure
+ extra_proc_mem = bytes2human(diff1 + diff2)
+ print("exta proc mem: %s" % extra_proc_mem, file=sys.stderr)
+ msg = "+%s after %s calls, +%s after another %s calls, "
+ msg += "+%s extra proc mem"
+ msg = msg % (
+ bytes2human(diff1), loops, bytes2human(diff2), ncalls,
+ extra_proc_mem)
+ self.fail(msg)
+
+ def execute_w_exc(self, exc, fun, *args, **kwargs):
+ """Convenience function which tests a callable raising
+ an exception.
+ """
+ def call():
+ self.assertRaises(exc, fun, *args, **kwargs)
+
+ self.execute(call)
+
+ @staticmethod
+ def _get_mem():
+ # By using USS memory it seems it's less likely to bump
+ # into false positives.
+ if LINUX or WINDOWS or OSX:
+ return thisproc.memory_full_info().uss
+ else:
+ return thisproc.memory_info().rss
+
+ @staticmethod
+ def _call(fun, *args, **kwargs):
+ fun(*args, **kwargs)
+
+
+# ===================================================================
+# Process class
+# ===================================================================
+
+
+class TestProcessObjectLeaks(TestMemLeak):
+ """Test leaks of Process class methods."""
+
+ proc = thisproc
+
+ def test_coverage(self):
+ skip = set((
+ "pid", "as_dict", "children", "cpu_affinity", "cpu_percent",
+ "ionice", "is_running", "kill", "memory_info_ex", "memory_percent",
+ "nice", "oneshot", "parent", "rlimit", "send_signal", "suspend",
+ "terminate", "wait"))
+ for name in dir(psutil.Process):
+ if name.startswith('_'):
+ continue
+ if name in skip:
+ continue
+ self.assertTrue(hasattr(self, "test_" + name), msg=name)
+
+ @skip_if_linux()
+ def test_name(self):
+ self.execute(self.proc.name)
+
+ @skip_if_linux()
+ def test_cmdline(self):
+ self.execute(self.proc.cmdline)
+
+ @skip_if_linux()
+ def test_exe(self):
+ self.execute(self.proc.exe)
+
+ @skip_if_linux()
+ def test_ppid(self):
+ self.execute(self.proc.ppid)
+
+ @unittest.skipIf(not POSIX, "POSIX only")
+ @skip_if_linux()
+ def test_uids(self):
+ self.execute(self.proc.uids)
+
+ @unittest.skipIf(not POSIX, "POSIX only")
+ @skip_if_linux()
+ def test_gids(self):
+ self.execute(self.proc.gids)
+
+ @skip_if_linux()
+ def test_status(self):
+ self.execute(self.proc.status)
+
+ def test_nice_get(self):
+ self.execute(self.proc.nice)
+
+ def test_nice_set(self):
+ niceness = thisproc.nice()
+ self.execute(self.proc.nice, niceness)
+
+ @unittest.skipIf(not HAS_IONICE, "not supported")
+ def test_ionice_get(self):
+ self.execute(self.proc.ionice)
+
+ @unittest.skipIf(not HAS_IONICE, "not supported")
+ def test_ionice_set(self):
+ if WINDOWS:
+ value = thisproc.ionice()
+ self.execute(self.proc.ionice, value)
+ else:
+ self.execute(self.proc.ionice, psutil.IOPRIO_CLASS_NONE)
+ fun = functools.partial(cext.proc_ioprio_set, os.getpid(), -1, 0)
+ self.execute_w_exc(OSError, fun)
+
+ @unittest.skipIf(not HAS_PROC_IO_COUNTERS, "not supported")
+ @skip_if_linux()
+ def test_io_counters(self):
+ self.execute(self.proc.io_counters)
+
+ @unittest.skipIf(POSIX, "worthless on POSIX")
+ def test_username(self):
+ self.execute(self.proc.username)
+
+ @skip_if_linux()
+ def test_create_time(self):
+ self.execute(self.proc.create_time)
+
+ @skip_if_linux()
+ @skip_on_access_denied(only_if=OPENBSD)
+ def test_num_threads(self):
+ self.execute(self.proc.num_threads)
+
+ @unittest.skipIf(not WINDOWS, "WINDOWS only")
+ def test_num_handles(self):
+ self.execute(self.proc.num_handles)
+
+ @unittest.skipIf(not POSIX, "POSIX only")
+ @skip_if_linux()
+ def test_num_fds(self):
+ self.execute(self.proc.num_fds)
+
+ @skip_if_linux()
+ def test_num_ctx_switches(self):
+ self.execute(self.proc.num_ctx_switches)
+
+ @skip_if_linux()
+ @skip_on_access_denied(only_if=OPENBSD)
+ def test_threads(self):
+ self.execute(self.proc.threads)
+
+ @skip_if_linux()
+ def test_cpu_times(self):
+ self.execute(self.proc.cpu_times)
+
+ @skip_if_linux()
+ @unittest.skipIf(not HAS_PROC_CPU_NUM, "not supported")
+ def test_cpu_num(self):
+ self.execute(self.proc.cpu_num)
+
+ @skip_if_linux()
+ def test_memory_info(self):
+ self.execute(self.proc.memory_info)
+
+ @skip_if_linux()
+ def test_memory_full_info(self):
+ self.execute(self.proc.memory_full_info)
+
+ @unittest.skipIf(not POSIX, "POSIX only")
+ @skip_if_linux()
+ def test_terminal(self):
+ self.execute(self.proc.terminal)
+
+ @unittest.skipIf(POSIX and SKIP_PYTHON_IMPL,
+ "worthless on POSIX (pure python)")
+ def test_resume(self):
+ self.execute(self.proc.resume)
+
+ @skip_if_linux()
+ def test_cwd(self):
+ self.execute(self.proc.cwd)
+
+ @unittest.skipIf(not HAS_CPU_AFFINITY, "not supported")
+ def test_cpu_affinity_get(self):
+ self.execute(self.proc.cpu_affinity)
+
+ @unittest.skipIf(not HAS_CPU_AFFINITY, "not supported")
+ def test_cpu_affinity_set(self):
+ affinity = thisproc.cpu_affinity()
+ self.execute(self.proc.cpu_affinity, affinity)
+ if not TRAVIS:
+ self.execute_w_exc(ValueError, self.proc.cpu_affinity, [-1])
+
+ @skip_if_linux()
+ def test_open_files(self):
+ safe_rmpath(TESTFN) # needed after UNIX socket test has run
+ with open(TESTFN, 'w'):
+ self.execute(self.proc.open_files)
+
+ # OSX implementation is unbelievably slow
+ @unittest.skipIf(OSX, "too slow on OSX")
+ @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported")
+ @skip_if_linux()
+ def test_memory_maps(self):
+ self.execute(self.proc.memory_maps)
+
+ @unittest.skipIf(not LINUX, "LINUX only")
+ @unittest.skipIf(not HAS_RLIMIT, "not supported")
+ def test_rlimit_get(self):
+ self.execute(self.proc.rlimit, psutil.RLIMIT_NOFILE)
+
+ @unittest.skipIf(not LINUX, "LINUX only")
+ @unittest.skipIf(not HAS_RLIMIT, "not supported")
+ def test_rlimit_set(self):
+ limit = thisproc.rlimit(psutil.RLIMIT_NOFILE)
+ self.execute(self.proc.rlimit, psutil.RLIMIT_NOFILE, limit)
+ self.execute_w_exc(OSError, self.proc.rlimit, -1)
+
+ @skip_if_linux()
+ # Windows implementation is based on a single system-wide
+ # function (tested later).
+ @unittest.skipIf(WINDOWS, "worthless on WINDOWS")
+ def test_connections(self):
+ # TODO: UNIX sockets are temporarily implemented by parsing
+ # 'pfiles' cmd output; we don't want that part of the code to
+ # be executed.
+ with create_sockets():
+ kind = 'inet' if SUNOS else 'all'
+ self.execute(self.proc.connections, kind)
+
+ @unittest.skipIf(not HAS_ENVIRON, "not supported")
+ def test_environ(self):
+ self.execute(self.proc.environ)
+
+ @unittest.skipIf(not WINDOWS, "WINDOWS only")
+ def test_proc_info(self):
+ self.execute(cext.proc_info, os.getpid())
+
+
+class TestTerminatedProcessLeaks(TestProcessObjectLeaks):
+ """Repeat the tests above looking for leaks occurring when dealing
+ with terminated processes raising NoSuchProcess exception.
+ The C functions are still invoked but will follow different code
+ paths. We'll check those code paths.
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestTerminatedProcessLeaks, cls).setUpClass()
+ p = get_test_subprocess()
+ cls.proc = psutil.Process(p.pid)
+ cls.proc.kill()
+ cls.proc.wait()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestTerminatedProcessLeaks, cls).tearDownClass()
+ reap_children()
+
+ def _call(self, fun, *args, **kwargs):
+ try:
+ fun(*args, **kwargs)
+ except psutil.NoSuchProcess:
+ pass
+
+ if WINDOWS:
+
+ def test_kill(self):
+ self.execute(self.proc.kill)
+
+ def test_terminate(self):
+ self.execute(self.proc.terminate)
+
+ def test_suspend(self):
+ self.execute(self.proc.suspend)
+
+ def test_resume(self):
+ self.execute(self.proc.resume)
+
+ def test_wait(self):
+ self.execute(self.proc.wait)
+
+ def test_proc_info(self):
+ # test dual implementation
+ def call():
+ try:
+ return cext.proc_info(self.proc.pid)
+ except OSError as err:
+ if err.errno != errno.ESRCH:
+ raise
+
+ self.execute(call)
+
+
+# ===================================================================
+# system APIs
+# ===================================================================
+
+
+class TestModuleFunctionsLeaks(TestMemLeak):
+ """Test leaks of psutil module functions."""
+
+ def test_coverage(self):
+ skip = set((
+ "version_info", "__version__", "process_iter", "wait_procs",
+ "cpu_percent", "cpu_times_percent", "cpu_count"))
+ for name in psutil.__all__:
+ if not name.islower():
+ continue
+ if name in skip:
+ continue
+ self.assertTrue(hasattr(self, "test_" + name), msg=name)
+
+ # --- cpu
+
+ @skip_if_linux()
+ def test_cpu_count_logical(self):
+ self.execute(psutil.cpu_count, logical=True)
+
+ @skip_if_linux()
+ def test_cpu_count_physical(self):
+ self.execute(psutil.cpu_count, logical=False)
+
+ @skip_if_linux()
+ def test_cpu_times(self):
+ self.execute(psutil.cpu_times)
+
+ @skip_if_linux()
+ def test_per_cpu_times(self):
+ self.execute(psutil.cpu_times, percpu=True)
+
+ def test_cpu_stats(self):
+ self.execute(psutil.cpu_stats)
+
+ @skip_if_linux()
+ @unittest.skipIf(not HAS_CPU_FREQ, "not supported")
+ def test_cpu_freq(self):
+ self.execute(psutil.cpu_freq)
+
+ # --- mem
+
+ def test_virtual_memory(self):
+ self.execute(psutil.virtual_memory)
+
+ # TODO: remove this skip when this gets fixed
+ @unittest.skipIf(SUNOS,
+ "worthless on SUNOS (uses a subprocess)")
+ def test_swap_memory(self):
+ self.execute(psutil.swap_memory)
+
+ @unittest.skipIf(POSIX and SKIP_PYTHON_IMPL,
+ "worthless on POSIX (pure python)")
+ def test_pid_exists(self):
+ self.execute(psutil.pid_exists, os.getpid())
+
+ # --- disk
+
+ @unittest.skipIf(POSIX and SKIP_PYTHON_IMPL,
+ "worthless on POSIX (pure python)")
+ def test_disk_usage(self):
+ self.execute(psutil.disk_usage, '.')
+
+ def test_disk_partitions(self):
+ self.execute(psutil.disk_partitions)
+
+ @unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'),
+ '/proc/diskstats not available on this Linux version')
+ @skip_if_linux()
+ def test_disk_io_counters(self):
+ self.execute(psutil.disk_io_counters, nowrap=False)
+
+ # --- proc
+
+ @skip_if_linux()
+ def test_pids(self):
+ self.execute(psutil.pids)
+
+ # --- net
+
+ @skip_if_linux()
+ def test_net_io_counters(self):
+ self.execute(psutil.net_io_counters, nowrap=False)
+
+ @unittest.skipIf(LINUX,
+ "worthless on Linux (pure python)")
+ @unittest.skipIf(OSX and os.getuid() != 0, "need root access")
+ def test_net_connections(self):
+ with create_sockets():
+ self.execute(psutil.net_connections)
+
+ def test_net_if_addrs(self):
+ # Note: verified that on Windows this was a false positive.
+ self.execute(psutil.net_if_addrs,
+ tolerance_=80 * 1024 if WINDOWS else None)
+
+ @unittest.skipIf(TRAVIS, "EPERM on travis")
+ def test_net_if_stats(self):
+ self.execute(psutil.net_if_stats)
+
+ # --- sensors
+
+ @skip_if_linux()
+ @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported")
+ def test_sensors_battery(self):
+ self.execute(psutil.sensors_battery)
+
+ @skip_if_linux()
+ @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported")
+ def test_sensors_temperatures(self):
+ self.execute(psutil.sensors_temperatures)
+
+ @skip_if_linux()
+ @unittest.skipIf(not HAS_SENSORS_FANS, "not supported")
+ def test_sensors_fans(self):
+ self.execute(psutil.sensors_fans)
+
+ # --- others
+
+ @skip_if_linux()
+ def test_boot_time(self):
+ self.execute(psutil.boot_time)
+
+ # XXX - on Windows this produces a false positive
+ @unittest.skipIf(WINDOWS, "XXX produces a false positive on Windows")
+ def test_users(self):
+ self.execute(psutil.users)
+
+ if WINDOWS:
+
+ # --- win services
+
+ def test_win_service_iter(self):
+ self.execute(cext.winservice_enumerate)
+
+ def test_win_service_get(self):
+ pass
+
+ def test_win_service_get_config(self):
+ name = next(psutil.win_service_iter()).name()
+ self.execute(cext.winservice_query_config, name)
+
+ def test_win_service_get_status(self):
+ name = next(psutil.win_service_iter()).name()
+ self.execute(cext.winservice_query_status, name)
+
+ def test_win_service_get_description(self):
+ name = next(psutil.win_service_iter()).name()
+ self.execute(cext.winservice_query_descr, name)
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py
new file mode 100755
index 00000000..1d9067e7
--- /dev/null
+++ b/psutil/tests/test_misc.py
@@ -0,0 +1,1046 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Miscellaneous tests.
+"""
+
+import ast
+import collections
+import contextlib
+import errno
+import json
+import os
+import pickle
+import socket
+import stat
+
+from psutil import LINUX
+from psutil import POSIX
+from psutil import WINDOWS
+from psutil._common import memoize
+from psutil._common import memoize_when_activated
+from psutil._common import supports_ipv6
+from psutil._common import wrap_numbers
+from psutil._compat import PY3
+from psutil.tests import APPVEYOR
+from psutil.tests import bind_socket
+from psutil.tests import bind_unix_socket
+from psutil.tests import call_until
+from psutil.tests import chdir
+from psutil.tests import create_proc_children_pair
+from psutil.tests import create_sockets
+from psutil.tests import create_zombie_proc
+from psutil.tests import DEVNULL
+from psutil.tests import get_free_port
+from psutil.tests import get_test_subprocess
+from psutil.tests import HAS_BATTERY
+from psutil.tests import HAS_CONNECTIONS_UNIX
+from psutil.tests import HAS_MEMORY_FULL_INFO
+from psutil.tests import HAS_MEMORY_MAPS
+from psutil.tests import HAS_SENSORS_BATTERY
+from psutil.tests import HAS_SENSORS_FANS
+from psutil.tests import HAS_SENSORS_TEMPERATURES
+from psutil.tests import import_module_by_path
+from psutil.tests import is_namedtuple
+from psutil.tests import mock
+from psutil.tests import PYTHON_EXE
+from psutil.tests import reap_children
+from psutil.tests import reload_module
+from psutil.tests import retry
+from psutil.tests import ROOT_DIR
+from psutil.tests import run_test_module_by_name
+from psutil.tests import safe_mkdir
+from psutil.tests import safe_rmpath
+from psutil.tests import SCRIPTS_DIR
+from psutil.tests import sh
+from psutil.tests import tcp_socketpair
+from psutil.tests import TESTFN
+from psutil.tests import TOX
+from psutil.tests import TRAVIS
+from psutil.tests import unittest
+from psutil.tests import unix_socket_path
+from psutil.tests import unix_socketpair
+from psutil.tests import wait_for_file
+from psutil.tests import wait_for_pid
+import psutil
+import psutil.tests
+
+
+# ===================================================================
+# --- Misc / generic tests.
+# ===================================================================
+
+
+class TestMisc(unittest.TestCase):
+
+ def test_process__repr__(self, func=repr):
+ p = psutil.Process()
+ r = func(p)
+ self.assertIn("psutil.Process", r)
+ self.assertIn("pid=%s" % p.pid, r)
+ self.assertIn("name=", r)
+ self.assertIn(p.name(), r)
+ with mock.patch.object(psutil.Process, "name",
+ side_effect=psutil.ZombieProcess(os.getpid())):
+ p = psutil.Process()
+ r = func(p)
+ self.assertIn("pid=%s" % p.pid, r)
+ self.assertIn("zombie", r)
+ self.assertNotIn("name=", r)
+ with mock.patch.object(psutil.Process, "name",
+ side_effect=psutil.NoSuchProcess(os.getpid())):
+ p = psutil.Process()
+ r = func(p)
+ self.assertIn("pid=%s" % p.pid, r)
+ self.assertIn("terminated", r)
+ self.assertNotIn("name=", r)
+ with mock.patch.object(psutil.Process, "name",
+ side_effect=psutil.AccessDenied(os.getpid())):
+ p = psutil.Process()
+ r = func(p)
+ self.assertIn("pid=%s" % p.pid, r)
+ self.assertNotIn("name=", r)
+
+ def test_process__str__(self):
+ self.test_process__repr__(func=str)
+
+ def test_no_such_process__repr__(self, func=repr):
+ self.assertEqual(
+ repr(psutil.NoSuchProcess(321)),
+ "psutil.NoSuchProcess process no longer exists (pid=321)")
+ self.assertEqual(
+ repr(psutil.NoSuchProcess(321, name='foo')),
+ "psutil.NoSuchProcess process no longer exists (pid=321, "
+ "name='foo')")
+ self.assertEqual(
+ repr(psutil.NoSuchProcess(321, msg='foo')),
+ "psutil.NoSuchProcess foo")
+
+ def test_zombie_process__repr__(self, func=repr):
+ self.assertEqual(
+ repr(psutil.ZombieProcess(321)),
+ "psutil.ZombieProcess process still exists but it's a zombie "
+ "(pid=321)")
+ self.assertEqual(
+ repr(psutil.ZombieProcess(321, name='foo')),
+ "psutil.ZombieProcess process still exists but it's a zombie "
+ "(pid=321, name='foo')")
+ self.assertEqual(
+ repr(psutil.ZombieProcess(321, name='foo', ppid=1)),
+ "psutil.ZombieProcess process still exists but it's a zombie "
+ "(pid=321, name='foo', ppid=1)")
+ self.assertEqual(
+ repr(psutil.ZombieProcess(321, msg='foo')),
+ "psutil.ZombieProcess foo")
+
+ def test_access_denied__repr__(self, func=repr):
+ self.assertEqual(
+ repr(psutil.AccessDenied(321)),
+ "psutil.AccessDenied (pid=321)")
+ self.assertEqual(
+ repr(psutil.AccessDenied(321, name='foo')),
+ "psutil.AccessDenied (pid=321, name='foo')")
+ self.assertEqual(
+ repr(psutil.AccessDenied(321, msg='foo')),
+ "psutil.AccessDenied foo")
+
+ def test_timeout_expired__repr__(self, func=repr):
+ self.assertEqual(
+ repr(psutil.TimeoutExpired(321)),
+ "psutil.TimeoutExpired timeout after 321 seconds")
+ self.assertEqual(
+ repr(psutil.TimeoutExpired(321, pid=111)),
+ "psutil.TimeoutExpired timeout after 321 seconds (pid=111)")
+ self.assertEqual(
+ repr(psutil.TimeoutExpired(321, pid=111, name='foo')),
+ "psutil.TimeoutExpired timeout after 321 seconds "
+ "(pid=111, name='foo')")
+
+ def test_process__eq__(self):
+ p1 = psutil.Process()
+ p2 = psutil.Process()
+ self.assertEqual(p1, p2)
+ p2._ident = (0, 0)
+ self.assertNotEqual(p1, p2)
+ self.assertNotEqual(p1, 'foo')
+
+ def test_process__hash__(self):
+ s = set([psutil.Process(), psutil.Process()])
+ self.assertEqual(len(s), 1)
+
+ def test__all__(self):
+ dir_psutil = dir(psutil)
+ for name in dir_psutil:
+ if name in ('callable', 'error', 'namedtuple', 'tests',
+ 'long', 'test', 'NUM_CPUS', 'BOOT_TIME',
+ 'TOTAL_PHYMEM'):
+ continue
+ if not name.startswith('_'):
+ try:
+ __import__(name)
+ except ImportError:
+ if name not in psutil.__all__:
+ fun = getattr(psutil, name)
+ if fun is None:
+ continue
+ if (fun.__doc__ is not None and
+ 'deprecated' not in fun.__doc__.lower()):
+ self.fail('%r not in psutil.__all__' % name)
+
+ # Import 'star' will break if __all__ is inconsistent, see:
+ # https://github.com/giampaolo/psutil/issues/656
+ # Can't do `from psutil import *` as it won't work on python 3
+ # so we simply iterate over __all__.
+ for name in psutil.__all__:
+ self.assertIn(name, dir_psutil)
+
+ def test_version(self):
+ self.assertEqual('.'.join([str(x) for x in psutil.version_info]),
+ psutil.__version__)
+
+ def test_process_as_dict_no_new_names(self):
+ # See https://github.com/giampaolo/psutil/issues/813
+ p = psutil.Process()
+ p.foo = '1'
+ self.assertNotIn('foo', p.as_dict())
+
+ def test_memoize(self):
+ @memoize
+ def foo(*args, **kwargs):
+ "foo docstring"
+ calls.append(None)
+ return (args, kwargs)
+
+ calls = []
+ # no args
+ for x in range(2):
+ ret = foo()
+ expected = ((), {})
+ self.assertEqual(ret, expected)
+ self.assertEqual(len(calls), 1)
+ # with args
+ for x in range(2):
+ ret = foo(1)
+ expected = ((1, ), {})
+ self.assertEqual(ret, expected)
+ self.assertEqual(len(calls), 2)
+ # with args + kwargs
+ for x in range(2):
+ ret = foo(1, bar=2)
+ expected = ((1, ), {'bar': 2})
+ self.assertEqual(ret, expected)
+ self.assertEqual(len(calls), 3)
+ # clear cache
+ foo.cache_clear()
+ ret = foo()
+ expected = ((), {})
+ self.assertEqual(ret, expected)
+ self.assertEqual(len(calls), 4)
+ # docstring
+ self.assertEqual(foo.__doc__, "foo docstring")
+
+ def test_memoize_when_activated(self):
+ class Foo:
+
+ @memoize_when_activated
+ def foo(self):
+ calls.append(None)
+
+ f = Foo()
+ calls = []
+ f.foo()
+ f.foo()
+ self.assertEqual(len(calls), 2)
+
+ # activate
+ calls = []
+ f.foo.cache_activate()
+ f.foo()
+ f.foo()
+ self.assertEqual(len(calls), 1)
+
+ # deactivate
+ calls = []
+ f.foo.cache_deactivate()
+ f.foo()
+ f.foo()
+ self.assertEqual(len(calls), 2)
+
+ def test_parse_environ_block(self):
+ from psutil._common import parse_environ_block
+
+ def k(s):
+ return s.upper() if WINDOWS else s
+
+ self.assertEqual(parse_environ_block("a=1\0"),
+ {k("a"): "1"})
+ self.assertEqual(parse_environ_block("a=1\0b=2\0\0"),
+ {k("a"): "1", k("b"): "2"})
+ self.assertEqual(parse_environ_block("a=1\0b=\0\0"),
+ {k("a"): "1", k("b"): ""})
+ # ignore everything after \0\0
+ self.assertEqual(parse_environ_block("a=1\0b=2\0\0c=3\0"),
+ {k("a"): "1", k("b"): "2"})
+ # ignore everything that is not an assignment
+ self.assertEqual(parse_environ_block("xxx\0a=1\0"), {k("a"): "1"})
+ self.assertEqual(parse_environ_block("a=1\0=b=2\0"), {k("a"): "1"})
+ # do not fail if the block is incomplete
+ self.assertEqual(parse_environ_block("a=1\0b=2"), {k("a"): "1"})
+
+ def test_supports_ipv6(self):
+ self.addCleanup(supports_ipv6.cache_clear)
+ if supports_ipv6():
+ with mock.patch('psutil._common.socket') as s:
+ s.has_ipv6 = False
+ supports_ipv6.cache_clear()
+ assert not supports_ipv6()
+
+ supports_ipv6.cache_clear()
+ with mock.patch('psutil._common.socket.socket',
+ side_effect=socket.error) as s:
+ assert not supports_ipv6()
+ assert s.called
+
+ supports_ipv6.cache_clear()
+ with mock.patch('psutil._common.socket.socket',
+ side_effect=socket.gaierror) as s:
+ assert not supports_ipv6()
+ supports_ipv6.cache_clear()
+ assert s.called
+
+ supports_ipv6.cache_clear()
+ with mock.patch('psutil._common.socket.socket.bind',
+ side_effect=socket.gaierror) as s:
+ assert not supports_ipv6()
+ supports_ipv6.cache_clear()
+ assert s.called
+ else:
+ with self.assertRaises(Exception):
+ sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+ sock.bind(("::1", 0))
+
+ def test_isfile_strict(self):
+ from psutil._common import isfile_strict
+ this_file = os.path.abspath(__file__)
+ assert isfile_strict(this_file)
+ assert not isfile_strict(os.path.dirname(this_file))
+ with mock.patch('psutil._common.os.stat',
+ side_effect=OSError(errno.EPERM, "foo")):
+ self.assertRaises(OSError, isfile_strict, this_file)
+ with mock.patch('psutil._common.os.stat',
+ side_effect=OSError(errno.EACCES, "foo")):
+ self.assertRaises(OSError, isfile_strict, this_file)
+ with mock.patch('psutil._common.os.stat',
+ side_effect=OSError(errno.EINVAL, "foo")):
+ assert not isfile_strict(this_file)
+ with mock.patch('psutil._common.stat.S_ISREG', return_value=False):
+ assert not isfile_strict(this_file)
+
+ def test_serialization(self):
+ def check(ret):
+ if json is not None:
+ json.loads(json.dumps(ret))
+ a = pickle.dumps(ret)
+ b = pickle.loads(a)
+ self.assertEqual(ret, b)
+
+ check(psutil.Process().as_dict())
+ check(psutil.virtual_memory())
+ check(psutil.swap_memory())
+ check(psutil.cpu_times())
+ check(psutil.cpu_times_percent(interval=0))
+ check(psutil.net_io_counters())
+ if LINUX and not os.path.exists('/proc/diskstats'):
+ pass
+ else:
+ if not APPVEYOR:
+ check(psutil.disk_io_counters())
+ check(psutil.disk_partitions())
+ check(psutil.disk_usage(os.getcwd()))
+ check(psutil.users())
+
+ def test_setup_script(self):
+ setup_py = os.path.join(ROOT_DIR, 'setup.py')
+ if TRAVIS and not os.path.exists(setup_py):
+ return self.skipTest("can't find setup.py")
+ module = import_module_by_path(setup_py)
+ self.assertRaises(SystemExit, module.setup)
+ self.assertEqual(module.get_version(), psutil.__version__)
+
+ 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
+
+ def test_sanity_version_check(self):
+ # see: https://github.com/giampaolo/psutil/issues/564
+ with mock.patch(
+ "psutil._psplatform.cext.version", return_value="0.0.0"):
+ with self.assertRaises(ImportError) as cm:
+ reload_module(psutil)
+ self.assertIn("version conflict", str(cm.exception).lower())
+
+
+# ===================================================================
+# --- Tests for wrap_numbers() function.
+# ===================================================================
+
+
+nt = collections.namedtuple('foo', 'a b c')
+
+
+class TestWrapNumbers(unittest.TestCase):
+
+ def setUp(self):
+ wrap_numbers.cache_clear()
+
+ tearDown = setUp
+
+ def test_first_call(self):
+ input = {'disk1': nt(5, 5, 5)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+
+ def test_input_hasnt_changed(self):
+ input = {'disk1': nt(5, 5, 5)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+
+ def test_increase_but_no_wrap(self):
+ input = {'disk1': nt(5, 5, 5)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+ input = {'disk1': nt(10, 15, 20)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+ input = {'disk1': nt(20, 25, 30)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+ input = {'disk1': nt(20, 25, 30)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+
+ def test_wrap(self):
+ # let's say 100 is the threshold
+ input = {'disk1': nt(100, 100, 100)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+ # first wrap restarts from 10
+ input = {'disk1': nt(100, 100, 10)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'),
+ {'disk1': nt(100, 100, 110)})
+ # then it remains the same
+ input = {'disk1': nt(100, 100, 10)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'),
+ {'disk1': nt(100, 100, 110)})
+ # then it goes up
+ input = {'disk1': nt(100, 100, 90)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'),
+ {'disk1': nt(100, 100, 190)})
+ # then it wraps again
+ input = {'disk1': nt(100, 100, 20)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'),
+ {'disk1': nt(100, 100, 210)})
+ # and remains the same
+ input = {'disk1': nt(100, 100, 20)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'),
+ {'disk1': nt(100, 100, 210)})
+ # now wrap another num
+ input = {'disk1': nt(50, 100, 20)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'),
+ {'disk1': nt(150, 100, 210)})
+ # and again
+ input = {'disk1': nt(40, 100, 20)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'),
+ {'disk1': nt(190, 100, 210)})
+ # keep it the same
+ input = {'disk1': nt(40, 100, 20)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'),
+ {'disk1': nt(190, 100, 210)})
+
+ def test_changing_keys(self):
+ # Emulate a case where the second call to disk_io()
+ # (or whatever) provides a new disk, then the new disk
+ # disappears on the third call.
+ input = {'disk1': nt(5, 5, 5)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+ input = {'disk1': nt(5, 5, 5),
+ 'disk2': nt(7, 7, 7)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+ input = {'disk1': nt(8, 8, 8)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+
+ def test_changing_keys_w_wrap(self):
+ input = {'disk1': nt(50, 50, 50),
+ 'disk2': nt(100, 100, 100)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+ # disk 2 wraps
+ input = {'disk1': nt(50, 50, 50),
+ 'disk2': nt(100, 100, 10)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'),
+ {'disk1': nt(50, 50, 50),
+ 'disk2': nt(100, 100, 110)})
+ # disk 2 disappears
+ input = {'disk1': nt(50, 50, 50)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+
+ # then it appears again; the old wrap is supposed to be
+ # gone.
+ input = {'disk1': nt(50, 50, 50),
+ 'disk2': nt(100, 100, 100)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+ # remains the same
+ input = {'disk1': nt(50, 50, 50),
+ 'disk2': nt(100, 100, 100)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'), input)
+ # and then wraps again
+ input = {'disk1': nt(50, 50, 50),
+ 'disk2': nt(100, 100, 10)}
+ self.assertEqual(wrap_numbers(input, 'disk_io'),
+ {'disk1': nt(50, 50, 50),
+ 'disk2': nt(100, 100, 110)})
+
+ def test_real_data(self):
+ d = {'nvme0n1': (300, 508, 640, 1571, 5970, 1987, 2049, 451751, 47048),
+ 'nvme0n1p1': (1171, 2, 5600256, 1024, 516, 0, 0, 0, 8),
+ 'nvme0n1p2': (54, 54, 2396160, 5165056, 4, 24, 30, 1207, 28),
+ 'nvme0n1p3': (2389, 4539, 5154, 150, 4828, 1844, 2019, 398, 348)}
+ self.assertEqual(wrap_numbers(d, 'disk_io'), d)
+ self.assertEqual(wrap_numbers(d, 'disk_io'), d)
+ # decrease this ↓
+ d = {'nvme0n1': (100, 508, 640, 1571, 5970, 1987, 2049, 451751, 47048),
+ 'nvme0n1p1': (1171, 2, 5600256, 1024, 516, 0, 0, 0, 8),
+ 'nvme0n1p2': (54, 54, 2396160, 5165056, 4, 24, 30, 1207, 28),
+ 'nvme0n1p3': (2389, 4539, 5154, 150, 4828, 1844, 2019, 398, 348)}
+ out = wrap_numbers(d, 'disk_io')
+ self.assertEqual(out['nvme0n1'][0], 400)
+
+ # --- cache tests
+
+ def test_cache_first_call(self):
+ input = {'disk1': nt(5, 5, 5)}
+ wrap_numbers(input, 'disk_io')
+ cache = wrap_numbers.cache_info()
+ self.assertEqual(cache[0], {'disk_io': input})
+ self.assertEqual(cache[1], {'disk_io': {}})
+ self.assertEqual(cache[2], {'disk_io': {}})
+
+ def test_cache_call_twice(self):
+ input = {'disk1': nt(5, 5, 5)}
+ wrap_numbers(input, 'disk_io')
+ input = {'disk1': nt(10, 10, 10)}
+ wrap_numbers(input, 'disk_io')
+ cache = wrap_numbers.cache_info()
+ self.assertEqual(cache[0], {'disk_io': input})
+ self.assertEqual(
+ cache[1],
+ {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 0}})
+ self.assertEqual(cache[2], {'disk_io': {}})
+
+ def test_cache_wrap(self):
+ # let's say 100 is the threshold
+ input = {'disk1': nt(100, 100, 100)}
+ wrap_numbers(input, 'disk_io')
+
+ # first wrap restarts from 10
+ input = {'disk1': nt(100, 100, 10)}
+ wrap_numbers(input, 'disk_io')
+ cache = wrap_numbers.cache_info()
+ self.assertEqual(cache[0], {'disk_io': input})
+ self.assertEqual(
+ cache[1],
+ {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 100}})
+ self.assertEqual(cache[2], {'disk_io': {'disk1': set([('disk1', 2)])}})
+
+ def assert_():
+ cache = wrap_numbers.cache_info()
+ self.assertEqual(
+ cache[1],
+ {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0,
+ ('disk1', 2): 100}})
+ self.assertEqual(cache[2],
+ {'disk_io': {'disk1': set([('disk1', 2)])}})
+
+ # then it remains the same
+ input = {'disk1': nt(100, 100, 10)}
+ wrap_numbers(input, 'disk_io')
+ cache = wrap_numbers.cache_info()
+ self.assertEqual(cache[0], {'disk_io': input})
+ assert_()
+
+ # then it goes up
+ input = {'disk1': nt(100, 100, 90)}
+ wrap_numbers(input, 'disk_io')
+ cache = wrap_numbers.cache_info()
+ self.assertEqual(cache[0], {'disk_io': input})
+ assert_()
+
+ # then it wraps again
+ input = {'disk1': nt(100, 100, 20)}
+ wrap_numbers(input, 'disk_io')
+ cache = wrap_numbers.cache_info()
+ self.assertEqual(cache[0], {'disk_io': input})
+ self.assertEqual(
+ cache[1],
+ {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 190}})
+ self.assertEqual(cache[2], {'disk_io': {'disk1': set([('disk1', 2)])}})
+
+ def test_cache_changing_keys(self):
+ input = {'disk1': nt(5, 5, 5)}
+ wrap_numbers(input, 'disk_io')
+ input = {'disk1': nt(5, 5, 5),
+ 'disk2': nt(7, 7, 7)}
+ wrap_numbers(input, 'disk_io')
+ cache = wrap_numbers.cache_info()
+ self.assertEqual(cache[0], {'disk_io': input})
+ self.assertEqual(
+ cache[1],
+ {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 0}})
+ self.assertEqual(cache[2], {'disk_io': {}})
+
+ def test_cache_clear(self):
+ input = {'disk1': nt(5, 5, 5)}
+ wrap_numbers(input, 'disk_io')
+ wrap_numbers(input, 'disk_io')
+ wrap_numbers.cache_clear('disk_io')
+ self.assertEqual(wrap_numbers.cache_info(), ({}, {}, {}))
+ wrap_numbers.cache_clear('disk_io')
+ wrap_numbers.cache_clear('?!?')
+
+ @unittest.skipIf(
+ not psutil.disk_io_counters() or not psutil.net_io_counters(),
+ "no disks or NICs available")
+ def test_cache_clear_public_apis(self):
+ psutil.disk_io_counters()
+ psutil.net_io_counters()
+ caches = wrap_numbers.cache_info()
+ for cache in caches:
+ self.assertIn('psutil.disk_io_counters', cache)
+ self.assertIn('psutil.net_io_counters', cache)
+
+ psutil.disk_io_counters.cache_clear()
+ caches = wrap_numbers.cache_info()
+ for cache in caches:
+ self.assertIn('psutil.net_io_counters', cache)
+ self.assertNotIn('psutil.disk_io_counters', cache)
+
+ psutil.net_io_counters.cache_clear()
+ caches = wrap_numbers.cache_info()
+ self.assertEqual(caches, ({}, {}, {}))
+
+
+# ===================================================================
+# --- Example script tests
+# ===================================================================
+
+
+@unittest.skipIf(TOX, "can't test on TOX")
+# See: https://travis-ci.org/giampaolo/psutil/jobs/295224806
+@unittest.skipIf(TRAVIS and not os.path.exists(SCRIPTS_DIR),
+ "can't locate scripts directory")
+class TestScripts(unittest.TestCase):
+ """Tests for scripts in the "scripts" directory."""
+
+ @staticmethod
+ def assert_stdout(exe, *args, **kwargs):
+ exe = '%s' % os.path.join(SCRIPTS_DIR, exe)
+ cmd = [PYTHON_EXE, exe]
+ for arg in args:
+ cmd.append(arg)
+ try:
+ out = sh(cmd, **kwargs).strip()
+ except RuntimeError as err:
+ if 'AccessDenied' in str(err):
+ return str(err)
+ else:
+ raise
+ assert out, out
+ return out
+
+ @staticmethod
+ def assert_syntax(exe, args=None):
+ exe = os.path.join(SCRIPTS_DIR, exe)
+ if PY3:
+ f = open(exe, 'rt', encoding='utf8')
+ else:
+ f = open(exe, 'rt')
+ with f:
+ src = f.read()
+ ast.parse(src)
+
+ def test_coverage(self):
+ # make sure all example scripts have a test method defined
+ meths = dir(self)
+ for name in os.listdir(SCRIPTS_DIR):
+ if name.endswith('.py'):
+ if 'test_' + os.path.splitext(name)[0] not in meths:
+ # self.assert_stdout(name)
+ self.fail('no test defined for %r script'
+ % os.path.join(SCRIPTS_DIR, name))
+
+ @unittest.skipIf(not POSIX, "POSIX only")
+ def test_executable(self):
+ for name in os.listdir(SCRIPTS_DIR):
+ if name.endswith('.py'):
+ path = os.path.join(SCRIPTS_DIR, name)
+ if not stat.S_IXUSR & os.stat(path)[stat.ST_MODE]:
+ self.fail('%r is not executable' % path)
+
+ def test_disk_usage(self):
+ self.assert_stdout('disk_usage.py')
+
+ def test_free(self):
+ self.assert_stdout('free.py')
+
+ def test_meminfo(self):
+ self.assert_stdout('meminfo.py')
+
+ def test_procinfo(self):
+ self.assert_stdout('procinfo.py', str(os.getpid()))
+
+ # can't find users on APPVEYOR or TRAVIS
+ @unittest.skipIf(APPVEYOR or TRAVIS and not psutil.users(),
+ "unreliable on APPVEYOR or TRAVIS")
+ def test_who(self):
+ self.assert_stdout('who.py')
+
+ def test_ps(self):
+ self.assert_stdout('ps.py')
+
+ def test_pstree(self):
+ self.assert_stdout('pstree.py')
+
+ def test_netstat(self):
+ self.assert_stdout('netstat.py')
+
+ # permission denied on travis
+ @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
+ def test_ifconfig(self):
+ self.assert_stdout('ifconfig.py')
+
+ @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported")
+ def test_pmap(self):
+ self.assert_stdout('pmap.py', str(os.getpid()))
+
+ @unittest.skipIf(not HAS_MEMORY_FULL_INFO, "not supported")
+ def test_procsmem(self):
+ self.assert_stdout('procsmem.py', stderr=DEVNULL)
+
+ def test_killall(self):
+ self.assert_syntax('killall.py')
+
+ def test_nettop(self):
+ self.assert_syntax('nettop.py')
+
+ def test_top(self):
+ self.assert_syntax('top.py')
+
+ def test_iotop(self):
+ self.assert_syntax('iotop.py')
+
+ def test_pidof(self):
+ output = self.assert_stdout('pidof.py', psutil.Process().name())
+ self.assertIn(str(os.getpid()), output)
+
+ @unittest.skipIf(not WINDOWS, "WINDOWS only")
+ def test_winservices(self):
+ self.assert_stdout('winservices.py')
+
+ def test_cpu_distribution(self):
+ self.assert_syntax('cpu_distribution.py')
+
+ @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported")
+ @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
+ def test_temperatures(self):
+ self.assert_stdout('temperatures.py')
+
+ @unittest.skipIf(not HAS_SENSORS_FANS, "not supported")
+ @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
+ def test_fans(self):
+ self.assert_stdout('fans.py')
+
+ @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported")
+ @unittest.skipIf(not HAS_BATTERY, "no battery")
+ def test_battery(self):
+ self.assert_stdout('battery.py')
+
+ def test_sensors(self):
+ self.assert_stdout('sensors.py')
+
+
+# ===================================================================
+# --- Unit tests for test utilities.
+# ===================================================================
+
+
+class TestRetryDecorator(unittest.TestCase):
+
+ @mock.patch('time.sleep')
+ def test_retry_success(self, sleep):
+ # Fail 3 times out of 5; make sure the decorated fun returns.
+
+ @retry(retries=5, interval=1, logfun=None)
+ def foo():
+ while queue:
+ queue.pop()
+ 1 / 0
+ return 1
+
+ queue = list(range(3))
+ self.assertEqual(foo(), 1)
+ self.assertEqual(sleep.call_count, 3)
+
+ @mock.patch('time.sleep')
+ def test_retry_failure(self, sleep):
+ # Fail 6 times out of 5; th function is supposed to raise exc.
+
+ @retry(retries=5, interval=1, logfun=None)
+ def foo():
+ while queue:
+ queue.pop()
+ 1 / 0
+ return 1
+
+ queue = list(range(6))
+ self.assertRaises(ZeroDivisionError, foo)
+ self.assertEqual(sleep.call_count, 5)
+
+ @mock.patch('time.sleep')
+ def test_exception_arg(self, sleep):
+ @retry(exception=ValueError, interval=1)
+ def foo():
+ raise TypeError
+
+ self.assertRaises(TypeError, foo)
+ self.assertEqual(sleep.call_count, 0)
+
+ @mock.patch('time.sleep')
+ def test_no_interval_arg(self, sleep):
+ # if interval is not specified sleep is not supposed to be called
+
+ @retry(retries=5, interval=None, logfun=None)
+ def foo():
+ 1 / 0
+
+ self.assertRaises(ZeroDivisionError, foo)
+ self.assertEqual(sleep.call_count, 0)
+
+ @mock.patch('time.sleep')
+ def test_retries_arg(self, sleep):
+
+ @retry(retries=5, interval=1, logfun=None)
+ def foo():
+ 1 / 0
+
+ self.assertRaises(ZeroDivisionError, foo)
+ self.assertEqual(sleep.call_count, 5)
+
+ @mock.patch('time.sleep')
+ def test_retries_and_timeout_args(self, sleep):
+ self.assertRaises(ValueError, retry, retries=5, timeout=1)
+
+
+class TestSyncTestUtils(unittest.TestCase):
+
+ def tearDown(self):
+ safe_rmpath(TESTFN)
+
+ def test_wait_for_pid(self):
+ wait_for_pid(os.getpid())
+ nopid = max(psutil.pids()) + 99999
+ with mock.patch('psutil.tests.retry.__iter__', return_value=iter([0])):
+ self.assertRaises(psutil.NoSuchProcess, wait_for_pid, nopid)
+
+ def test_wait_for_file(self):
+ with open(TESTFN, 'w') as f:
+ f.write('foo')
+ wait_for_file(TESTFN)
+ assert not os.path.exists(TESTFN)
+
+ def test_wait_for_file_empty(self):
+ with open(TESTFN, 'w'):
+ pass
+ wait_for_file(TESTFN, empty=True)
+ assert not os.path.exists(TESTFN)
+
+ def test_wait_for_file_no_file(self):
+ with mock.patch('psutil.tests.retry.__iter__', return_value=iter([0])):
+ self.assertRaises(IOError, wait_for_file, TESTFN)
+
+ def test_wait_for_file_no_delete(self):
+ with open(TESTFN, 'w') as f:
+ f.write('foo')
+ wait_for_file(TESTFN, delete=False)
+ assert os.path.exists(TESTFN)
+
+ def test_call_until(self):
+ ret = call_until(lambda: 1, "ret == 1")
+ self.assertEqual(ret, 1)
+
+
+class TestFSTestUtils(unittest.TestCase):
+
+ def setUp(self):
+ safe_rmpath(TESTFN)
+
+ tearDown = setUp
+
+ def test_safe_mkdir(self):
+ safe_mkdir(TESTFN)
+ assert os.path.isdir(TESTFN)
+ safe_mkdir(TESTFN)
+ assert os.path.isdir(TESTFN)
+
+ def test_safe_rmpath(self):
+ # test file is removed
+ open(TESTFN, 'w').close()
+ safe_rmpath(TESTFN)
+ assert not os.path.exists(TESTFN)
+ # test no exception if path does not exist
+ safe_rmpath(TESTFN)
+ # test dir is removed
+ os.mkdir(TESTFN)
+ safe_rmpath(TESTFN)
+ assert not os.path.exists(TESTFN)
+ # test other exceptions are raised
+ with mock.patch('psutil.tests.os.stat',
+ side_effect=OSError(errno.EINVAL, "")) as m:
+ with self.assertRaises(OSError):
+ safe_rmpath(TESTFN)
+ assert m.called
+
+ def test_chdir(self):
+ base = os.getcwd()
+ os.mkdir(TESTFN)
+ with chdir(TESTFN):
+ self.assertEqual(os.getcwd(), os.path.join(base, TESTFN))
+ self.assertEqual(os.getcwd(), base)
+
+
+class TestProcessUtils(unittest.TestCase):
+
+ def test_reap_children(self):
+ subp = get_test_subprocess()
+ p = psutil.Process(subp.pid)
+ assert p.is_running()
+ reap_children()
+ assert not p.is_running()
+ assert not psutil.tests._pids_started
+ assert not psutil.tests._subprocesses_started
+
+ def test_create_proc_children_pair(self):
+ p1, p2 = create_proc_children_pair()
+ self.assertNotEqual(p1.pid, p2.pid)
+ assert p1.is_running()
+ assert p2.is_running()
+ children = psutil.Process().children(recursive=True)
+ self.assertEqual(len(children), 2)
+ self.assertIn(p1, children)
+ self.assertIn(p2, children)
+ self.assertEqual(p1.ppid(), os.getpid())
+ self.assertEqual(p2.ppid(), p1.pid)
+
+ # make sure both of them are cleaned up
+ reap_children()
+ assert not p1.is_running()
+ assert not p2.is_running()
+ assert not psutil.tests._pids_started
+ assert not psutil.tests._subprocesses_started
+
+ @unittest.skipIf(not POSIX, "POSIX only")
+ def test_create_zombie_proc(self):
+ zpid = create_zombie_proc()
+ self.addCleanup(reap_children, recursive=True)
+ p = psutil.Process(zpid)
+ self.assertEqual(p.status(), psutil.STATUS_ZOMBIE)
+
+
+class TestNetUtils(unittest.TestCase):
+
+ def bind_socket(self):
+ port = get_free_port()
+ with contextlib.closing(bind_socket(addr=('', port))) as s:
+ self.assertEqual(s.getsockname()[1], port)
+
+ @unittest.skipIf(not POSIX, "POSIX only")
+ def test_bind_unix_socket(self):
+ with unix_socket_path() as name:
+ sock = bind_unix_socket(name)
+ with contextlib.closing(sock):
+ self.assertEqual(sock.family, socket.AF_UNIX)
+ self.assertEqual(sock.type, socket.SOCK_STREAM)
+ self.assertEqual(sock.getsockname(), name)
+ assert os.path.exists(name)
+ assert stat.S_ISSOCK(os.stat(name).st_mode)
+ # UDP
+ with unix_socket_path() as name:
+ sock = bind_unix_socket(name, type=socket.SOCK_DGRAM)
+ with contextlib.closing(sock):
+ self.assertEqual(sock.type, socket.SOCK_DGRAM)
+
+ def tcp_tcp_socketpair(self):
+ addr = ("127.0.0.1", get_free_port())
+ server, client = tcp_socketpair(socket.AF_INET, addr=addr)
+ with contextlib.closing(server):
+ with contextlib.closing(client):
+ # Ensure they are connected and the positions are
+ # correct.
+ self.assertEqual(server.getsockname(), addr)
+ self.assertEqual(client.getpeername(), addr)
+ self.assertNotEqual(client.getsockname(), addr)
+
+ @unittest.skipIf(not POSIX, "POSIX only")
+ def test_unix_socketpair(self):
+ p = psutil.Process()
+ num_fds = p.num_fds()
+ assert not p.connections(kind='unix')
+ with unix_socket_path() as name:
+ server, client = unix_socketpair(name)
+ try:
+ assert os.path.exists(name)
+ assert stat.S_ISSOCK(os.stat(name).st_mode)
+ self.assertEqual(p.num_fds() - num_fds, 2)
+ self.assertEqual(len(p.connections(kind='unix')), 2)
+ self.assertEqual(server.getsockname(), name)
+ self.assertEqual(client.getpeername(), name)
+ finally:
+ client.close()
+ server.close()
+
+ def test_create_sockets(self):
+ with create_sockets() as socks:
+ fams = collections.defaultdict(int)
+ types = collections.defaultdict(int)
+ for s in socks:
+ fams[s.family] += 1
+ # work around http://bugs.python.org/issue30204
+ types[s.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE)] += 1
+ self.assertGreaterEqual(fams[socket.AF_INET], 2)
+ if supports_ipv6():
+ self.assertGreaterEqual(fams[socket.AF_INET6], 2)
+ if POSIX and HAS_CONNECTIONS_UNIX:
+ self.assertGreaterEqual(fams[socket.AF_UNIX], 2)
+ self.assertGreaterEqual(types[socket.SOCK_STREAM], 2)
+ self.assertGreaterEqual(types[socket.SOCK_DGRAM], 2)
+
+
+class TestOtherUtils(unittest.TestCase):
+
+ def test_is_namedtuple(self):
+ assert is_namedtuple(collections.namedtuple('foo', 'a b c')(1, 2, 3))
+ assert not is_namedtuple(tuple())
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_osx.py b/psutil/tests/test_osx.py
new file mode 100755
index 00000000..bcb2ba4e
--- /dev/null
+++ b/psutil/tests/test_osx.py
@@ -0,0 +1,303 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""OSX specific tests."""
+
+import os
+import re
+import time
+
+import psutil
+from psutil import OSX
+from psutil.tests import create_zombie_proc
+from psutil.tests import get_test_subprocess
+from psutil.tests import HAS_BATTERY
+from psutil.tests import MEMORY_TOLERANCE
+from psutil.tests import reap_children
+from psutil.tests import retry_before_failing
+from psutil.tests import run_test_module_by_name
+from psutil.tests import sh
+from psutil.tests import unittest
+
+
+PAGESIZE = os.sysconf("SC_PAGE_SIZE") if OSX else None
+
+
+def sysctl(cmdline):
+ """Expects a sysctl command with an argument and parse the result
+ returning only the value of interest.
+ """
+ out = sh(cmdline)
+ result = out.split()[1]
+ try:
+ return int(result)
+ except ValueError:
+ return result
+
+
+def vm_stat(field):
+ """Wrapper around 'vm_stat' cmdline utility."""
+ out = sh('vm_stat')
+ for line in out.split('\n'):
+ if field in line:
+ break
+ else:
+ raise ValueError("line not found")
+ return int(re.search(r'\d+', line).group(0)) * PAGESIZE
+
+
+# http://code.activestate.com/recipes/578019/
+def human2bytes(s):
+ SYMBOLS = {
+ 'customary': ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'),
+ }
+ init = s
+ num = ""
+ while s and s[0:1].isdigit() or s[0:1] == '.':
+ num += s[0]
+ s = s[1:]
+ num = float(num)
+ letter = s.strip()
+ for name, sset in SYMBOLS.items():
+ if letter in sset:
+ break
+ else:
+ if letter == 'k':
+ sset = SYMBOLS['customary']
+ letter = letter.upper()
+ else:
+ raise ValueError("can't interpret %r" % init)
+ prefix = {sset[0]: 1}
+ for i, s in enumerate(sset[1:]):
+ prefix[s] = 1 << (i + 1) * 10
+ return int(num * prefix[letter])
+
+
+@unittest.skipIf(not OSX, "OSX only")
+class TestProcess(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.pid = get_test_subprocess().pid
+
+ @classmethod
+ def tearDownClass(cls):
+ reap_children()
+
+ def test_process_create_time(self):
+ output = sh("ps -o lstart -p %s" % self.pid)
+ start_ps = output.replace('STARTED', '').strip()
+ hhmmss = start_ps.split(' ')[-2]
+ year = start_ps.split(' ')[-1]
+ start_psutil = psutil.Process(self.pid).create_time()
+ self.assertEqual(
+ hhmmss,
+ time.strftime("%H:%M:%S", time.localtime(start_psutil)))
+ self.assertEqual(
+ year,
+ time.strftime("%Y", time.localtime(start_psutil)))
+
+
+@unittest.skipIf(not OSX, "OSX only")
+class TestZombieProcessAPIs(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ zpid = create_zombie_proc()
+ cls.p = psutil.Process(zpid)
+
+ @classmethod
+ def tearDownClass(cls):
+ reap_children(recursive=True)
+
+ def test_pidtask_info(self):
+ self.assertEqual(self.p.status(), psutil.STATUS_ZOMBIE)
+ self.p.ppid()
+ self.p.uids()
+ self.p.gids()
+ self.p.terminal()
+ self.p.create_time()
+
+ def test_exe(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.exe)
+
+ def test_cmdline(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.cmdline)
+
+ def test_environ(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.environ)
+
+ def test_cwd(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.cwd)
+
+ def test_memory_full_info(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.memory_full_info)
+
+ def test_cpu_times(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.cpu_times)
+
+ def test_num_ctx_switches(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.num_ctx_switches)
+
+ def test_num_threads(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.num_threads)
+
+ def test_open_files(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.open_files)
+
+ def test_connections(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.connections)
+
+ def test_num_fds(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.num_fds)
+
+ def test_threads(self):
+ self.assertRaises((psutil.ZombieProcess, psutil.AccessDenied),
+ self.p.threads)
+
+ def test_memory_maps(self):
+ self.assertRaises(psutil.ZombieProcess, self.p.memory_maps)
+
+
+@unittest.skipIf(not OSX, "OSX only")
+class TestSystemAPIs(unittest.TestCase):
+
+ # --- disk
+
+ def test_disks(self):
+ # test psutil.disk_usage() and psutil.disk_partitions()
+ # against "df -a"
+ def df(path):
+ out = sh('df -k "%s"' % path).strip()
+ lines = out.split('\n')
+ lines.pop(0)
+ line = lines.pop(0)
+ dev, total, used, free = line.split()[:4]
+ if dev == 'none':
+ dev = ''
+ total = int(total) * 1024
+ used = int(used) * 1024
+ free = int(free) * 1024
+ return dev, total, used, free
+
+ for part in psutil.disk_partitions(all=False):
+ usage = psutil.disk_usage(part.mountpoint)
+ dev, total, used, free = df(part.mountpoint)
+ self.assertEqual(part.device, dev)
+ self.assertEqual(usage.total, total)
+ # 10 MB tollerance
+ if abs(usage.free - free) > 10 * 1024 * 1024:
+ self.fail("psutil=%s, df=%s" % usage.free, free)
+ if abs(usage.used - used) > 10 * 1024 * 1024:
+ self.fail("psutil=%s, df=%s" % usage.used, used)
+
+ # --- cpu
+
+ def test_cpu_count_logical(self):
+ num = sysctl("sysctl hw.logicalcpu")
+ self.assertEqual(num, psutil.cpu_count(logical=True))
+
+ def test_cpu_count_physical(self):
+ num = sysctl("sysctl hw.physicalcpu")
+ self.assertEqual(num, psutil.cpu_count(logical=False))
+
+ def test_cpu_freq(self):
+ freq = psutil.cpu_freq()
+ self.assertEqual(
+ freq.current * 1000 * 1000, sysctl("sysctl hw.cpufrequency"))
+ self.assertEqual(
+ freq.min * 1000 * 1000, sysctl("sysctl hw.cpufrequency_min"))
+ self.assertEqual(
+ freq.max * 1000 * 1000, sysctl("sysctl hw.cpufrequency_max"))
+
+ # --- virtual mem
+
+ def test_vmem_total(self):
+ sysctl_hwphymem = sysctl('sysctl hw.memsize')
+ self.assertEqual(sysctl_hwphymem, psutil.virtual_memory().total)
+
+ @retry_before_failing()
+ def test_vmem_free(self):
+ vmstat_val = vm_stat("free")
+ psutil_val = psutil.virtual_memory().free
+ self.assertAlmostEqual(psutil_val, vmstat_val, delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_vmem_available(self):
+ vmstat_val = vm_stat("inactive") + vm_stat("free")
+ psutil_val = psutil.virtual_memory().available
+ self.assertAlmostEqual(psutil_val, vmstat_val, delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_vmem_active(self):
+ vmstat_val = vm_stat("active")
+ psutil_val = psutil.virtual_memory().active
+ self.assertAlmostEqual(psutil_val, vmstat_val, delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_vmem_inactive(self):
+ vmstat_val = vm_stat("inactive")
+ psutil_val = psutil.virtual_memory().inactive
+ self.assertAlmostEqual(psutil_val, vmstat_val, delta=MEMORY_TOLERANCE)
+
+ @retry_before_failing()
+ def test_vmem_wired(self):
+ vmstat_val = vm_stat("wired")
+ psutil_val = psutil.virtual_memory().wired
+ self.assertAlmostEqual(psutil_val, vmstat_val, delta=MEMORY_TOLERANCE)
+
+ # --- swap mem
+
+ @retry_before_failing()
+ def test_swapmem_sin(self):
+ vmstat_val = vm_stat("Pageins")
+ psutil_val = psutil.swap_memory().sin
+ self.assertEqual(psutil_val, vmstat_val)
+
+ @retry_before_failing()
+ def test_swapmem_sout(self):
+ vmstat_val = vm_stat("Pageout")
+ psutil_val = psutil.swap_memory().sout
+ self.assertEqual(psutil_val, vmstat_val)
+
+ # Not very reliable.
+ # def test_swapmem_total(self):
+ # out = sh('sysctl vm.swapusage')
+ # out = out.replace('vm.swapusage: ', '')
+ # total, used, free = re.findall('\d+.\d+\w', out)
+ # psutil_smem = psutil.swap_memory()
+ # self.assertEqual(psutil_smem.total, human2bytes(total))
+ # self.assertEqual(psutil_smem.used, human2bytes(used))
+ # self.assertEqual(psutil_smem.free, human2bytes(free))
+
+ # --- network
+
+ def test_net_if_stats(self):
+ for name, stats in psutil.net_if_stats().items():
+ try:
+ out = sh("ifconfig %s" % name)
+ except RuntimeError:
+ pass
+ else:
+ self.assertEqual(stats.isup, 'RUNNING' in out, msg=out)
+ self.assertEqual(stats.mtu,
+ int(re.findall(r'mtu (\d+)', out)[0]))
+
+ # --- sensors_battery
+
+ @unittest.skipIf(not HAS_BATTERY, "no battery")
+ def test_sensors_battery(self):
+ out = sh("pmset -g batt")
+ percent = re.search("(\d+)%", out).group(1)
+ drawing_from = re.search("Now drawing from '([^']+)'", out).group(1)
+ power_plugged = drawing_from == "AC Power"
+ psutil_result = psutil.sensors_battery()
+ self.assertEqual(psutil_result.power_plugged, power_plugged)
+ self.assertEqual(psutil_result.percent, int(percent))
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py
new file mode 100755
index 00000000..e9a6f5f6
--- /dev/null
+++ b/psutil/tests/test_posix.py
@@ -0,0 +1,418 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""POSIX specific tests."""
+
+import datetime
+import errno
+import os
+import re
+import subprocess
+import sys
+import time
+
+import psutil
+from psutil import AIX
+from psutil import BSD
+from psutil import LINUX
+from psutil import OPENBSD
+from psutil import OSX
+from psutil import POSIX
+from psutil import SUNOS
+from psutil._compat import PY3
+from psutil.tests import APPVEYOR
+from psutil.tests import get_kernel_version
+from psutil.tests import get_test_subprocess
+from psutil.tests import mock
+from psutil.tests import PYTHON_EXE
+from psutil.tests import reap_children
+from psutil.tests import retry_before_failing
+from psutil.tests import run_test_module_by_name
+from psutil.tests import sh
+from psutil.tests import skip_on_access_denied
+from psutil.tests import TRAVIS
+from psutil.tests import unittest
+from psutil.tests import wait_for_pid
+from psutil.tests import which
+
+
+def ps(cmd):
+ """Expects a ps command with a -o argument and parse the result
+ returning only the value of interest.
+ """
+ if not LINUX:
+ cmd = cmd.replace(" --no-headers ", " ")
+ if SUNOS:
+ cmd = cmd.replace("-o start", "-o stime")
+ if AIX:
+ cmd = cmd.replace("-o rss", "-o rssize")
+ output = sh(cmd)
+ if not LINUX:
+ output = output.split('\n')[1].strip()
+ try:
+ return int(output)
+ except ValueError:
+ return output
+
+# ps "-o" field names differ wildly between platforms.
+# "comm" means "only executable name" but is not available on BSD platforms.
+# "args" means "command with all its arguments", and is also not available
+# on BSD platforms.
+# "command" is like "args" on most platforms, but like "comm" on AIX,
+# and not available on SUNOS.
+# so for the executable name we can use "comm" on Solaris and split "command"
+# on other platforms.
+# to get the cmdline (with args) we have to use "args" on AIX and
+# Solaris, and can use "command" on all others.
+
+
+def ps_name(pid):
+ field = "command"
+ if SUNOS:
+ field = "comm"
+ return ps("ps --no-headers -o %s -p %s" % (field, pid)).split(' ')[0]
+
+
+def ps_args(pid):
+ field = "command"
+ if AIX or SUNOS:
+ field = "args"
+ return ps("ps --no-headers -o %s -p %s" % (field, pid))
+
+
+@unittest.skipIf(not POSIX, "POSIX only")
+class TestProcess(unittest.TestCase):
+ """Compare psutil results against 'ps' command line utility (mainly)."""
+
+ @classmethod
+ def setUpClass(cls):
+ cls.pid = get_test_subprocess([PYTHON_EXE, "-E", "-O"],
+ stdin=subprocess.PIPE).pid
+ wait_for_pid(cls.pid)
+
+ @classmethod
+ def tearDownClass(cls):
+ reap_children()
+
+ def test_ppid(self):
+ ppid_ps = ps("ps --no-headers -o ppid -p %s" % self.pid)
+ ppid_psutil = psutil.Process(self.pid).ppid()
+ self.assertEqual(ppid_ps, ppid_psutil)
+
+ def test_uid(self):
+ uid_ps = ps("ps --no-headers -o uid -p %s" % self.pid)
+ uid_psutil = psutil.Process(self.pid).uids().real
+ self.assertEqual(uid_ps, uid_psutil)
+
+ def test_gid(self):
+ gid_ps = ps("ps --no-headers -o rgid -p %s" % self.pid)
+ gid_psutil = psutil.Process(self.pid).gids().real
+ self.assertEqual(gid_ps, gid_psutil)
+
+ def test_username(self):
+ username_ps = ps("ps --no-headers -o user -p %s" % self.pid)
+ username_psutil = psutil.Process(self.pid).username()
+ self.assertEqual(username_ps, username_psutil)
+
+ def test_username_no_resolution(self):
+ # Emulate a case where the system can't resolve the uid to
+ # a username in which case psutil is supposed to return
+ # the stringified uid.
+ p = psutil.Process()
+ with mock.patch("psutil.pwd.getpwuid", side_effect=KeyError) as fun:
+ self.assertEqual(p.username(), str(p.uids().real))
+ assert fun.called
+
+ @skip_on_access_denied()
+ @retry_before_failing()
+ def test_rss_memory(self):
+ # give python interpreter some time to properly initialize
+ # so that the results are the same
+ time.sleep(0.1)
+ rss_ps = ps("ps --no-headers -o rss -p %s" % self.pid)
+ rss_psutil = psutil.Process(self.pid).memory_info()[0] / 1024
+ self.assertEqual(rss_ps, rss_psutil)
+
+ @skip_on_access_denied()
+ @retry_before_failing()
+ def test_vsz_memory(self):
+ # give python interpreter some time to properly initialize
+ # so that the results are the same
+ time.sleep(0.1)
+ vsz_ps = ps("ps --no-headers -o vsz -p %s" % self.pid)
+ vsz_psutil = psutil.Process(self.pid).memory_info()[1] / 1024
+ self.assertEqual(vsz_ps, vsz_psutil)
+
+ def test_name(self):
+ name_ps = ps_name(self.pid)
+ # remove path if there is any, from the command
+ name_ps = os.path.basename(name_ps).lower()
+ name_psutil = psutil.Process(self.pid).name().lower()
+ # ...because of how we calculate PYTHON_EXE; on OSX this may
+ # be "pythonX.Y".
+ name_ps = re.sub(r"\d.\d", "", name_ps)
+ name_psutil = re.sub(r"\d.\d", "", name_psutil)
+ self.assertEqual(name_ps, name_psutil)
+
+ def test_name_long(self):
+ # On UNIX the kernel truncates the name to the first 15
+ # characters. In such a case psutil tries to determine the
+ # full name from the cmdline.
+ name = "long-program-name"
+ cmdline = ["long-program-name-extended", "foo", "bar"]
+ with mock.patch("psutil._psplatform.Process.name",
+ return_value=name):
+ with mock.patch("psutil._psplatform.Process.cmdline",
+ return_value=cmdline):
+ p = psutil.Process()
+ self.assertEqual(p.name(), "long-program-name-extended")
+
+ def test_name_long_cmdline_ad_exc(self):
+ # Same as above but emulates a case where cmdline() raises
+ # AccessDenied in which case psutil is supposed to return
+ # the truncated name instead of crashing.
+ name = "long-program-name"
+ with mock.patch("psutil._psplatform.Process.name",
+ return_value=name):
+ with mock.patch("psutil._psplatform.Process.cmdline",
+ side_effect=psutil.AccessDenied(0, "")):
+ p = psutil.Process()
+ self.assertEqual(p.name(), "long-program-name")
+
+ def test_name_long_cmdline_nsp_exc(self):
+ # Same as above but emulates a case where cmdline() raises NSP
+ # which is supposed to propagate.
+ name = "long-program-name"
+ with mock.patch("psutil._psplatform.Process.name",
+ return_value=name):
+ with mock.patch("psutil._psplatform.Process.cmdline",
+ side_effect=psutil.NoSuchProcess(0, "")):
+ p = psutil.Process()
+ self.assertRaises(psutil.NoSuchProcess, p.name)
+
+ @unittest.skipIf(OSX or BSD, 'ps -o start not available')
+ def test_create_time(self):
+ time_ps = ps("ps --no-headers -o start -p %s" % self.pid).split(' ')[0]
+ time_psutil = psutil.Process(self.pid).create_time()
+ time_psutil_tstamp = datetime.datetime.fromtimestamp(
+ time_psutil).strftime("%H:%M:%S")
+ # sometimes ps shows the time rounded up instead of down, so we check
+ # for both possible values
+ round_time_psutil = round(time_psutil)
+ round_time_psutil_tstamp = datetime.datetime.fromtimestamp(
+ round_time_psutil).strftime("%H:%M:%S")
+ self.assertIn(time_ps, [time_psutil_tstamp, round_time_psutil_tstamp])
+
+ def test_exe(self):
+ ps_pathname = ps_name(self.pid)
+ psutil_pathname = psutil.Process(self.pid).exe()
+ try:
+ self.assertEqual(ps_pathname, psutil_pathname)
+ except AssertionError:
+ # certain platforms such as BSD are more accurate returning:
+ # "/usr/local/bin/python2.7"
+ # ...instead of:
+ # "/usr/local/bin/python"
+ # We do not want to consider this difference in accuracy
+ # an error.
+ adjusted_ps_pathname = ps_pathname[:len(ps_pathname)]
+ self.assertEqual(ps_pathname, adjusted_ps_pathname)
+
+ def test_cmdline(self):
+ ps_cmdline = ps_args(self.pid)
+ psutil_cmdline = " ".join(psutil.Process(self.pid).cmdline())
+ self.assertEqual(ps_cmdline, psutil_cmdline)
+
+ # On SUNOS "ps" reads niceness /proc/pid/psinfo which returns an
+ # incorrect value (20); the real deal is getpriority(2) which
+ # returns 0; psutil relies on it, see:
+ # https://github.com/giampaolo/psutil/issues/1082
+ # AIX has the same issue
+ @unittest.skipIf(SUNOS, "not reliable on SUNOS")
+ @unittest.skipIf(AIX, "not reliable on AIX")
+ def test_nice(self):
+ ps_nice = ps("ps --no-headers -o nice -p %s" % self.pid)
+ psutil_nice = psutil.Process().nice()
+ self.assertEqual(ps_nice, psutil_nice)
+
+ def test_num_fds(self):
+ # Note: this fails from time to time; I'm keen on thinking
+ # it doesn't mean something is broken
+ def call(p, attr):
+ args = ()
+ attr = getattr(p, name, None)
+ if attr is not None and callable(attr):
+ if name == 'rlimit':
+ args = (psutil.RLIMIT_NOFILE,)
+ attr(*args)
+ else:
+ attr
+
+ p = psutil.Process(os.getpid())
+ failures = []
+ ignored_names = ['terminate', 'kill', 'suspend', 'resume', 'nice',
+ 'send_signal', 'wait', 'children', 'as_dict',
+ 'memory_info_ex']
+ if LINUX and get_kernel_version() < (2, 6, 36):
+ ignored_names.append('rlimit')
+ if LINUX and get_kernel_version() < (2, 6, 23):
+ ignored_names.append('num_ctx_switches')
+ for name in dir(psutil.Process):
+ if (name.startswith('_') or name in ignored_names):
+ continue
+ else:
+ try:
+ num1 = p.num_fds()
+ for x in range(2):
+ call(p, name)
+ num2 = p.num_fds()
+ except psutil.AccessDenied:
+ pass
+ else:
+ if abs(num2 - num1) > 1:
+ fail = "failure while processing Process.%s method " \
+ "(before=%s, after=%s)" % (name, num1, num2)
+ failures.append(fail)
+ if failures:
+ self.fail('\n' + '\n'.join(failures))
+
+
+@unittest.skipIf(not POSIX, "POSIX only")
+class TestSystemAPIs(unittest.TestCase):
+ """Test some system APIs."""
+
+ @retry_before_failing()
+ def test_pids(self):
+ # Note: this test might fail if the OS is starting/killing
+ # other processes in the meantime
+ if SUNOS or AIX:
+ cmd = ["ps", "-A", "-o", "pid"]
+ else:
+ cmd = ["ps", "ax", "-o", "pid"]
+ p = get_test_subprocess(cmd, stdout=subprocess.PIPE)
+ output = p.communicate()[0].strip()
+ assert p.poll() == 0
+ if PY3:
+ output = str(output, sys.stdout.encoding)
+ pids_ps = []
+ for line in output.split('\n')[1:]:
+ if line:
+ pid = int(line.split()[0].strip())
+ pids_ps.append(pid)
+ # remove ps subprocess pid which is supposed to be dead in meantime
+ pids_ps.remove(p.pid)
+ pids_psutil = psutil.pids()
+ pids_ps.sort()
+ pids_psutil.sort()
+
+ # on OSX and OPENBSD ps doesn't show pid 0
+ if OSX or OPENBSD and 0 not in pids_ps:
+ pids_ps.insert(0, 0)
+ self.assertEqual(pids_ps, pids_psutil)
+
+ # for some reason ifconfig -a does not report all interfaces
+ # returned by psutil
+ @unittest.skipIf(SUNOS, "unreliable on SUNOS")
+ @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
+ @unittest.skipIf(not which('ifconfig'), "no ifconfig cmd")
+ def test_nic_names(self):
+ output = sh("ifconfig -a")
+ for nic in psutil.net_io_counters(pernic=True).keys():
+ for line in output.split():
+ if line.startswith(nic):
+ break
+ else:
+ self.fail(
+ "couldn't find %s nic in 'ifconfig -a' output\n%s" % (
+ nic, output))
+
+ # can't find users on APPVEYOR or TRAVIS
+ @unittest.skipIf(APPVEYOR or TRAVIS and not psutil.users(),
+ "unreliable on APPVEYOR or TRAVIS")
+ @retry_before_failing()
+ def test_users(self):
+ out = sh("who")
+ lines = out.split('\n')
+ users = [x.split()[0] for x in lines]
+ terminals = [x.split()[1] for x in lines]
+ self.assertEqual(len(users), len(psutil.users()))
+ for u in psutil.users():
+ self.assertIn(u.name, users)
+ self.assertIn(u.terminal, terminals)
+
+ def test_pid_exists_let_raise(self):
+ # According to "man 2 kill" possible error values for kill
+ # are (EINVAL, EPERM, ESRCH). Test that any other errno
+ # results in an exception.
+ with mock.patch("psutil._psposix.os.kill",
+ side_effect=OSError(errno.EBADF, "")) as m:
+ self.assertRaises(OSError, psutil._psposix.pid_exists, os.getpid())
+ assert m.called
+
+ def test_os_waitpid_let_raise(self):
+ # os.waitpid() is supposed to catch EINTR and ECHILD only.
+ # Test that any other errno results in an exception.
+ with mock.patch("psutil._psposix.os.waitpid",
+ side_effect=OSError(errno.EBADF, "")) as m:
+ self.assertRaises(OSError, psutil._psposix.wait_pid, os.getpid())
+ assert m.called
+
+ def test_os_waitpid_eintr(self):
+ # os.waitpid() is supposed to "retry" on EINTR.
+ with mock.patch("psutil._psposix.os.waitpid",
+ side_effect=OSError(errno.EINTR, "")) as m:
+ self.assertRaises(
+ psutil._psposix.TimeoutExpired,
+ psutil._psposix.wait_pid, os.getpid(), timeout=0.01)
+ assert m.called
+
+ def test_os_waitpid_bad_ret_status(self):
+ # Simulate os.waitpid() returning a bad status.
+ with mock.patch("psutil._psposix.os.waitpid",
+ return_value=(1, -1)) as m:
+ self.assertRaises(ValueError,
+ psutil._psposix.wait_pid, os.getpid())
+ assert m.called
+
+ # AIX can return '-' in df output instead of numbers, e.g. for /proc
+ @unittest.skipIf(AIX, "unreliable on AIX")
+ def test_disk_usage(self):
+ def df(device):
+ out = sh("df -k %s" % device).strip()
+ line = out.split('\n')[1]
+ fields = line.split()
+ total = int(fields[1]) * 1024
+ used = int(fields[2]) * 1024
+ free = int(fields[3]) * 1024
+ percent = float(fields[4].replace('%', ''))
+ return (total, used, free, percent)
+
+ tolerance = 4 * 1024 * 1024 # 4MB
+ for part in psutil.disk_partitions(all=False):
+ usage = psutil.disk_usage(part.mountpoint)
+ try:
+ total, used, free, percent = df(part.device)
+ except RuntimeError as err:
+ # see:
+ # https://travis-ci.org/giampaolo/psutil/jobs/138338464
+ # https://travis-ci.org/giampaolo/psutil/jobs/138343361
+ err = str(err).lower()
+ if "no such file or directory" in err or \
+ "raw devices not supported" in err or \
+ "permission denied" in err:
+ continue
+ else:
+ raise
+ else:
+ self.assertAlmostEqual(usage.total, total, delta=tolerance)
+ self.assertAlmostEqual(usage.used, used, delta=tolerance)
+ self.assertAlmostEqual(usage.free, free, delta=tolerance)
+ self.assertAlmostEqual(usage.percent, percent, delta=1)
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
new file mode 100755
index 00000000..33557b05
--- /dev/null
+++ b/psutil/tests/test_process.py
@@ -0,0 +1,1567 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Tests for psutil.Process class."""
+
+import collections
+import errno
+import getpass
+import itertools
+import os
+import signal
+import socket
+import subprocess
+import sys
+import tempfile
+import textwrap
+import time
+import types
+
+import psutil
+
+from psutil import AIX
+from psutil import BSD
+from psutil import LINUX
+from psutil import NETBSD
+from psutil import OPENBSD
+from psutil import OSX
+from psutil import POSIX
+from psutil import SUNOS
+from psutil import WINDOWS
+from psutil._compat import long
+from psutil._compat import PY3
+from psutil.tests import APPVEYOR
+from psutil.tests import call_until
+from psutil.tests import copyload_shared_lib
+from psutil.tests import create_exe
+from psutil.tests import create_proc_children_pair
+from psutil.tests import create_zombie_proc
+from psutil.tests import enum
+from psutil.tests import get_test_subprocess
+from psutil.tests import get_winver
+from psutil.tests import HAS_CPU_AFFINITY
+from psutil.tests import HAS_ENVIRON
+from psutil.tests import HAS_IONICE
+from psutil.tests import HAS_MEMORY_MAPS
+from psutil.tests import HAS_PROC_CPU_NUM
+from psutil.tests import HAS_PROC_IO_COUNTERS
+from psutil.tests import HAS_RLIMIT
+from psutil.tests import HAS_THREADS
+from psutil.tests import mock
+from psutil.tests import PYPY
+from psutil.tests import PYTHON_EXE
+from psutil.tests import reap_children
+from psutil.tests import retry_before_failing
+from psutil.tests import run_test_module_by_name
+from psutil.tests import safe_rmpath
+from psutil.tests import sh
+from psutil.tests import skip_on_access_denied
+from psutil.tests import skip_on_not_implemented
+from psutil.tests import TESTFILE_PREFIX
+from psutil.tests import TESTFN
+from psutil.tests import ThreadTask
+from psutil.tests import TRAVIS
+from psutil.tests import unittest
+from psutil.tests import wait_for_pid
+from psutil.tests import WIN_VISTA
+
+
+# ===================================================================
+# --- psutil.Process class tests
+# ===================================================================
+
+class TestProcess(unittest.TestCase):
+ """Tests for psutil.Process class."""
+
+ def setUp(self):
+ safe_rmpath(TESTFN)
+
+ def tearDown(self):
+ reap_children()
+
+ def test_pid(self):
+ p = psutil.Process()
+ self.assertEqual(p.pid, os.getpid())
+ sproc = get_test_subprocess()
+ self.assertEqual(psutil.Process(sproc.pid).pid, sproc.pid)
+ with self.assertRaises(AttributeError):
+ p.pid = 33
+
+ def test_kill(self):
+ sproc = get_test_subprocess()
+ test_pid = sproc.pid
+ p = psutil.Process(test_pid)
+ p.kill()
+ sig = p.wait()
+ self.assertFalse(psutil.pid_exists(test_pid))
+ if POSIX:
+ self.assertEqual(sig, -signal.SIGKILL)
+
+ def test_terminate(self):
+ sproc = get_test_subprocess()
+ test_pid = sproc.pid
+ p = psutil.Process(test_pid)
+ p.terminate()
+ sig = p.wait()
+ self.assertFalse(psutil.pid_exists(test_pid))
+ if POSIX:
+ self.assertEqual(sig, -signal.SIGTERM)
+
+ def test_send_signal(self):
+ sig = signal.SIGKILL if POSIX else signal.SIGTERM
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ p.send_signal(sig)
+ exit_sig = p.wait()
+ 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, "")):
+ with self.assertRaises(psutil.NoSuchProcess):
+ p.send_signal(sig)
+ #
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ p.send_signal(sig)
+ with mock.patch('psutil.os.kill',
+ side_effect=OSError(errno.EPERM, "")):
+ with self.assertRaises(psutil.AccessDenied):
+ psutil.Process().send_signal(sig)
+ # Sending a signal to process with PID 0 is not allowed as
+ # it would affect every process in the process group of
+ # the calling process (os.getpid()) instead of PID 0").
+ if 0 in psutil.pids():
+ p = psutil.Process(0)
+ self.assertRaises(ValueError, p.send_signal, signal.SIGTERM)
+
+ def test_wait(self):
+ # check exit code signal
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ p.kill()
+ code = p.wait()
+ if POSIX:
+ self.assertEqual(code, -signal.SIGKILL)
+ else:
+ self.assertEqual(code, signal.SIGTERM)
+ self.assertFalse(p.is_running())
+
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ p.terminate()
+ code = p.wait()
+ if POSIX:
+ self.assertEqual(code, -signal.SIGTERM)
+ else:
+ self.assertEqual(code, signal.SIGTERM)
+ self.assertFalse(p.is_running())
+
+ # check sys.exit() code
+ code = "import time, sys; time.sleep(0.01); sys.exit(5);"
+ sproc = get_test_subprocess([PYTHON_EXE, "-c", code])
+ p = psutil.Process(sproc.pid)
+ self.assertEqual(p.wait(), 5)
+ self.assertFalse(p.is_running())
+
+ # Test wait() issued twice.
+ # It is not supposed to raise NSP when the process is gone.
+ # On UNIX this should return None, on Windows it should keep
+ # returning the exit code.
+ sproc = get_test_subprocess([PYTHON_EXE, "-c", code])
+ p = psutil.Process(sproc.pid)
+ self.assertEqual(p.wait(), 5)
+ self.assertIn(p.wait(), (5, None))
+
+ # test timeout
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ p.name()
+ self.assertRaises(psutil.TimeoutExpired, p.wait, 0.01)
+
+ # timeout < 0 not allowed
+ self.assertRaises(ValueError, p.wait, -1)
+
+ def test_wait_non_children(self):
+ # Test wait() against a process which is not our direct
+ # child.
+ p1, p2 = create_proc_children_pair()
+ self.assertRaises(psutil.TimeoutExpired, p1.wait, 0.01)
+ self.assertRaises(psutil.TimeoutExpired, p2.wait, 0.01)
+ # We also terminate the direct child otherwise the
+ # grandchild will hang until the parent is gone.
+ p1.terminate()
+ p2.terminate()
+ ret1 = p1.wait()
+ ret2 = p2.wait()
+ if POSIX:
+ self.assertEqual(ret1, -signal.SIGTERM)
+ # For processes which are not our children we're supposed
+ # to get None.
+ self.assertEqual(ret2, None)
+ else:
+ self.assertEqual(ret1, signal.SIGTERM)
+ self.assertEqual(ret1, signal.SIGTERM)
+
+ def test_wait_timeout_0(self):
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ self.assertRaises(psutil.TimeoutExpired, p.wait, 0)
+ p.kill()
+ stop_at = time.time() + 2
+ while True:
+ try:
+ code = p.wait(0)
+ except psutil.TimeoutExpired:
+ if time.time() >= stop_at:
+ raise
+ else:
+ break
+ if POSIX:
+ self.assertEqual(code, -signal.SIGKILL)
+ else:
+ self.assertEqual(code, signal.SIGTERM)
+ self.assertFalse(p.is_running())
+
+ def test_cpu_percent(self):
+ p = psutil.Process()
+ p.cpu_percent(interval=0.001)
+ p.cpu_percent(interval=0.001)
+ for x in range(100):
+ percent = p.cpu_percent(interval=None)
+ self.assertIsInstance(percent, float)
+ self.assertGreaterEqual(percent, 0.0)
+ if not POSIX:
+ self.assertLessEqual(percent, 100.0)
+ else:
+ self.assertGreaterEqual(percent, 0.0)
+ with self.assertRaises(ValueError):
+ p.cpu_percent(interval=-1)
+
+ def test_cpu_percent_numcpus_none(self):
+ # See: https://github.com/giampaolo/psutil/issues/1087
+ with mock.patch('psutil.cpu_count', return_value=None) as m:
+ psutil.Process().cpu_percent()
+ assert m.called
+
+ def test_cpu_times(self):
+ times = psutil.Process().cpu_times()
+ assert (times.user > 0.0) or (times.system > 0.0), times
+ assert (times.children_user >= 0.0), times
+ assert (times.children_system >= 0.0), times
+ # make sure returned values can be pretty printed with strftime
+ for name in times._fields:
+ time.strftime("%H:%M:%S", time.localtime(getattr(times, name)))
+
+ def test_cpu_times_2(self):
+ user_time, kernel_time = psutil.Process().cpu_times()[:2]
+ utime, ktime = os.times()[:2]
+
+ # Use os.times()[:2] as base values to compare our results
+ # using a tolerance of +/- 0.1 seconds.
+ # It will fail if the difference between the values is > 0.1s.
+ if (max([user_time, utime]) - min([user_time, utime])) > 0.1:
+ self.fail("expected: %s, found: %s" % (utime, user_time))
+
+ if (max([kernel_time, ktime]) - min([kernel_time, ktime])) > 0.1:
+ self.fail("expected: %s, found: %s" % (ktime, kernel_time))
+
+ @unittest.skipIf(not HAS_PROC_CPU_NUM, "not supported")
+ def test_cpu_num(self):
+ p = psutil.Process()
+ num = p.cpu_num()
+ self.assertGreaterEqual(num, 0)
+ if psutil.cpu_count() == 1:
+ self.assertEqual(num, 0)
+ self.assertIn(p.cpu_num(), range(psutil.cpu_count()))
+
+ def test_create_time(self):
+ sproc = get_test_subprocess()
+ now = time.time()
+ p = psutil.Process(sproc.pid)
+ create_time = p.create_time()
+
+ # Use time.time() as base value to compare our result using a
+ # tolerance of +/- 1 second.
+ # It will fail if the difference between the values is > 2s.
+ difference = abs(create_time - now)
+ if difference > 2:
+ self.fail("expected: %s, found: %s, difference: %s"
+ % (now, create_time, difference))
+
+ # make sure returned value can be pretty printed with strftime
+ time.strftime("%Y %m %d %H:%M:%S", time.localtime(p.create_time()))
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ @unittest.skipIf(TRAVIS, 'not reliable on TRAVIS')
+ def test_terminal(self):
+ terminal = psutil.Process().terminal()
+ if sys.stdin.isatty() or sys.stdout.isatty():
+ tty = os.path.realpath(sh('tty'))
+ self.assertEqual(terminal, tty)
+ else:
+ self.assertIsNone(terminal)
+
+ @unittest.skipIf(not HAS_PROC_IO_COUNTERS, 'not supported')
+ @skip_on_not_implemented(only_if=LINUX)
+ def test_io_counters(self):
+ p = psutil.Process()
+
+ # test reads
+ io1 = p.io_counters()
+ with open(PYTHON_EXE, 'rb') as f:
+ f.read()
+ io2 = p.io_counters()
+ if not BSD and not AIX:
+ self.assertGreater(io2.read_count, io1.read_count)
+ self.assertEqual(io2.write_count, io1.write_count)
+ if LINUX:
+ self.assertGreater(io2.read_chars, io1.read_chars)
+ self.assertEqual(io2.write_chars, io1.write_chars)
+ else:
+ self.assertGreaterEqual(io2.read_bytes, io1.read_bytes)
+ self.assertGreaterEqual(io2.write_bytes, io1.write_bytes)
+
+ # test writes
+ io1 = p.io_counters()
+ with tempfile.TemporaryFile(prefix=TESTFILE_PREFIX) as f:
+ if PY3:
+ f.write(bytes("x" * 1000000, 'ascii'))
+ else:
+ f.write("x" * 1000000)
+ io2 = p.io_counters()
+ self.assertGreaterEqual(io2.write_count, io1.write_count)
+ self.assertGreaterEqual(io2.write_bytes, io1.write_bytes)
+ self.assertGreaterEqual(io2.read_count, io1.read_count)
+ self.assertGreaterEqual(io2.read_bytes, io1.read_bytes)
+ if LINUX:
+ self.assertGreater(io2.write_chars, io1.write_chars)
+ self.assertGreaterEqual(io2.read_chars, io1.read_chars)
+
+ # sanity check
+ for i in range(len(io2)):
+ if BSD and i >= 2:
+ # On BSD read_bytes and write_bytes are always set to -1.
+ continue
+ self.assertGreaterEqual(io2[i], 0)
+ self.assertGreaterEqual(io2[i], 0)
+
+ @unittest.skipIf(not HAS_IONICE, "not supported")
+ @unittest.skipIf(WINDOWS and get_winver() < WIN_VISTA, 'not supported')
+ def test_ionice(self):
+ if LINUX:
+ from psutil import (IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT,
+ IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE)
+ self.assertEqual(IOPRIO_CLASS_NONE, 0)
+ self.assertEqual(IOPRIO_CLASS_RT, 1)
+ self.assertEqual(IOPRIO_CLASS_BE, 2)
+ self.assertEqual(IOPRIO_CLASS_IDLE, 3)
+ p = psutil.Process()
+ try:
+ p.ionice(2)
+ ioclass, value = p.ionice()
+ if enum is not None:
+ self.assertIsInstance(ioclass, enum.IntEnum)
+ self.assertEqual(ioclass, 2)
+ self.assertEqual(value, 4)
+ #
+ p.ionice(3)
+ ioclass, value = p.ionice()
+ self.assertEqual(ioclass, 3)
+ self.assertEqual(value, 0)
+ #
+ p.ionice(2, 0)
+ ioclass, value = p.ionice()
+ self.assertEqual(ioclass, 2)
+ self.assertEqual(value, 0)
+ p.ionice(2, 7)
+ ioclass, value = p.ionice()
+ self.assertEqual(ioclass, 2)
+ self.assertEqual(value, 7)
+ finally:
+ p.ionice(IOPRIO_CLASS_NONE)
+ else:
+ p = psutil.Process()
+ original = p.ionice()
+ self.assertIsInstance(original, int)
+ try:
+ value = 0 # very low
+ if original == value:
+ value = 1 # low
+ p.ionice(value)
+ self.assertEqual(p.ionice(), value)
+ finally:
+ p.ionice(original)
+
+ @unittest.skipIf(not HAS_IONICE, "not supported")
+ @unittest.skipIf(WINDOWS and get_winver() < WIN_VISTA, 'not supported')
+ def test_ionice_errs(self):
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ if LINUX:
+ 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.assertRaisesRegex(
+ ValueError, "can't specify value with IOPRIO_CLASS_NONE",
+ p.ionice, psutil.IOPRIO_CLASS_NONE, 1)
+ self.assertRaisesRegex(
+ ValueError, "can't specify value with IOPRIO_CLASS_IDLE",
+ p.ionice, psutil.IOPRIO_CLASS_IDLE, 1)
+ self.assertRaisesRegex(
+ ValueError, "'ioclass' argument must be specified",
+ p.ionice, value=1)
+ else:
+ self.assertRaises(ValueError, p.ionice, 3)
+ self.assertRaises(TypeError, p.ionice, 2, 1)
+
+ @unittest.skipIf(not HAS_RLIMIT, "not supported")
+ def test_rlimit_get(self):
+ import resource
+ p = psutil.Process(os.getpid())
+ names = [x for x in dir(psutil) if x.startswith('RLIMIT')]
+ assert names, names
+ for name in names:
+ value = getattr(psutil, name)
+ self.assertGreaterEqual(value, 0)
+ if name in dir(resource):
+ self.assertEqual(value, getattr(resource, name))
+ # XXX - On PyPy RLIMIT_INFINITY returned by
+ # resource.getrlimit() is reported as a very big long
+ # number instead of -1. It looks like a bug with PyPy.
+ if PYPY:
+ continue
+ self.assertEqual(p.rlimit(value), resource.getrlimit(value))
+ else:
+ ret = p.rlimit(value)
+ self.assertEqual(len(ret), 2)
+ self.assertGreaterEqual(ret[0], -1)
+ self.assertGreaterEqual(ret[1], -1)
+
+ @unittest.skipIf(not HAS_RLIMIT, "not supported")
+ def test_rlimit_set(self):
+ sproc = get_test_subprocess()
+ 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))
+
+ @unittest.skipIf(not HAS_RLIMIT, "not supported")
+ def test_rlimit(self):
+ p = psutil.Process()
+ soft, hard = p.rlimit(psutil.RLIMIT_FSIZE)
+ try:
+ p.rlimit(psutil.RLIMIT_FSIZE, (1024, hard))
+ with open(TESTFN, "wb") as f:
+ f.write(b"X" * 1024)
+ # write() or flush() doesn't always cause the exception
+ # but close() will.
+ with self.assertRaises(IOError) as exc:
+ with open(TESTFN, "wb") as f:
+ f.write(b"X" * 1025)
+ self.assertEqual(exc.exception.errno if PY3 else exc.exception[0],
+ errno.EFBIG)
+ finally:
+ p.rlimit(psutil.RLIMIT_FSIZE, (soft, hard))
+ self.assertEqual(p.rlimit(psutil.RLIMIT_FSIZE), (soft, hard))
+
+ @unittest.skipIf(not HAS_RLIMIT, "not supported")
+ def test_rlimit_infinity(self):
+ # First set a limit, then re-set it by specifying INFINITY
+ # and assume we overridden the previous limit.
+ p = psutil.Process()
+ soft, hard = p.rlimit(psutil.RLIMIT_FSIZE)
+ try:
+ p.rlimit(psutil.RLIMIT_FSIZE, (1024, hard))
+ p.rlimit(psutil.RLIMIT_FSIZE, (psutil.RLIM_INFINITY, hard))
+ with open(TESTFN, "wb") as f:
+ f.write(b"X" * 2048)
+ finally:
+ p.rlimit(psutil.RLIMIT_FSIZE, (soft, hard))
+ self.assertEqual(p.rlimit(psutil.RLIMIT_FSIZE), (soft, hard))
+
+ @unittest.skipIf(not HAS_RLIMIT, "not supported")
+ def test_rlimit_infinity_value(self):
+ # RLIMIT_FSIZE should be RLIM_INFINITY, which will be a really
+ # big number on a platform with large file support. On these
+ # platforms we need to test that the get/setrlimit functions
+ # properly convert the number to a C long long and that the
+ # conversion doesn't raise an error.
+ p = psutil.Process()
+ soft, hard = p.rlimit(psutil.RLIMIT_FSIZE)
+ self.assertEqual(psutil.RLIM_INFINITY, hard)
+ p.rlimit(psutil.RLIMIT_FSIZE, (soft, hard))
+
+ def test_num_threads(self):
+ # on certain platforms such as Linux we might test for exact
+ # thread number, since we always have with 1 thread per process,
+ # but this does not apply across all platforms (OSX, Windows)
+ p = psutil.Process()
+ if OPENBSD:
+ try:
+ step1 = p.num_threads()
+ except psutil.AccessDenied:
+ raise unittest.SkipTest("on OpenBSD this requires root access")
+ else:
+ step1 = p.num_threads()
+
+ with ThreadTask():
+ step2 = p.num_threads()
+ self.assertEqual(step2, step1 + 1)
+
+ @unittest.skipIf(not WINDOWS, 'WINDOWS only')
+ def test_num_handles(self):
+ # a better test is done later into test/_windows.py
+ p = psutil.Process()
+ self.assertGreater(p.num_handles(), 0)
+
+ @unittest.skipIf(not HAS_THREADS, 'not supported')
+ def test_threads(self):
+ p = psutil.Process()
+ if OPENBSD:
+ try:
+ step1 = p.threads()
+ except psutil.AccessDenied:
+ raise unittest.SkipTest("on OpenBSD this requires root access")
+ else:
+ step1 = p.threads()
+
+ with ThreadTask():
+ step2 = p.threads()
+ self.assertEqual(len(step2), len(step1) + 1)
+ # on Linux, first thread id is supposed to be this process
+ if LINUX:
+ self.assertEqual(step2[0].id, os.getpid())
+ athread = step2[0]
+ # test named tuple
+ self.assertEqual(athread.id, athread[0])
+ self.assertEqual(athread.user_time, athread[1])
+ self.assertEqual(athread.system_time, athread[2])
+
+ @retry_before_failing()
+ @skip_on_access_denied(only_if=OSX)
+ @unittest.skipIf(not HAS_THREADS, 'not supported')
+ def test_threads_2(self):
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ if OPENBSD:
+ try:
+ p.threads()
+ except psutil.AccessDenied:
+ raise unittest.SkipTest(
+ "on OpenBSD this requires root access")
+ self.assertAlmostEqual(
+ p.cpu_times().user,
+ sum([x.user_time for x in p.threads()]), delta=0.1)
+ self.assertAlmostEqual(
+ p.cpu_times().system,
+ sum([x.system_time for x in p.threads()]), delta=0.1)
+
+ def test_memory_info(self):
+ p = psutil.Process()
+
+ # step 1 - get a base value to compare our results
+ rss1, vms1 = p.memory_info()[:2]
+ percent1 = p.memory_percent()
+ self.assertGreater(rss1, 0)
+ self.assertGreater(vms1, 0)
+
+ # step 2 - allocate some memory
+ memarr = [None] * 1500000
+
+ rss2, vms2 = p.memory_info()[:2]
+ percent2 = p.memory_percent()
+
+ # step 3 - make sure that the memory usage bumped up
+ self.assertGreater(rss2, rss1)
+ self.assertGreaterEqual(vms2, vms1) # vms might be equal
+ self.assertGreater(percent2, percent1)
+ del memarr
+
+ if WINDOWS:
+ mem = p.memory_info()
+ self.assertEqual(mem.rss, mem.wset)
+ self.assertEqual(mem.vms, mem.pagefile)
+
+ mem = p.memory_info()
+ for name in mem._fields:
+ self.assertGreaterEqual(getattr(mem, name), 0)
+
+ def test_memory_full_info(self):
+ total = psutil.virtual_memory().total
+ mem = psutil.Process().memory_full_info()
+ for name in mem._fields:
+ value = getattr(mem, name)
+ self.assertGreaterEqual(value, 0, msg=(name, value))
+ self.assertLessEqual(value, total, msg=(name, value, total))
+ if LINUX or WINDOWS or OSX:
+ self.assertGreaterEqual(mem.uss, 0)
+ if LINUX:
+ self.assertGreaterEqual(mem.pss, 0)
+ self.assertGreaterEqual(mem.swap, 0)
+
+ @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported")
+ def test_memory_maps(self):
+ p = psutil.Process()
+ maps = p.memory_maps()
+ paths = [x for x in maps]
+ self.assertEqual(len(paths), len(set(paths)))
+ ext_maps = p.memory_maps(grouped=False)
+
+ for nt in maps:
+ if not nt.path.startswith('['):
+ assert os.path.isabs(nt.path), nt.path
+ if POSIX:
+ try:
+ assert os.path.exists(nt.path) or \
+ os.path.islink(nt.path), nt.path
+ except AssertionError:
+ if not LINUX:
+ raise
+ else:
+ # https://github.com/giampaolo/psutil/issues/759
+ with open('/proc/self/smaps') as f:
+ data = f.read()
+ if "%s (deleted)" % nt.path not in data:
+ raise
+ else:
+ # XXX - On Windows we have this strange behavior with
+ # 64 bit dlls: they are visible via explorer but cannot
+ # be accessed via os.stat() (wtf?).
+ if '64' not in os.path.basename(nt.path):
+ assert os.path.exists(nt.path), nt.path
+ for nt in ext_maps:
+ for fname in nt._fields:
+ value = getattr(nt, fname)
+ if fname == 'path':
+ continue
+ elif fname in ('addr', 'perms'):
+ assert value, value
+ else:
+ self.assertIsInstance(value, (int, long))
+ assert value >= 0, value
+
+ @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported")
+ def test_memory_maps_lists_lib(self):
+ # Make sure a newly loaded shared lib is listed.
+ with copyload_shared_lib() as path:
+ def normpath(p):
+ return os.path.realpath(os.path.normcase(p))
+ libpaths = [normpath(x.path)
+ for x in psutil.Process().memory_maps()]
+ self.assertIn(normpath(path), libpaths)
+
+ def test_memory_percent(self):
+ p = psutil.Process()
+ ret = p.memory_percent()
+ assert 0 <= ret <= 100, ret
+ ret = p.memory_percent(memtype='vms')
+ assert 0 <= ret <= 100, ret
+ assert 0 <= ret <= 100, ret
+ self.assertRaises(ValueError, p.memory_percent, memtype="?!?")
+ if LINUX or OSX or WINDOWS:
+ ret = p.memory_percent(memtype='uss')
+ assert 0 <= ret <= 100, ret
+ assert 0 <= ret <= 100, ret
+
+ def test_is_running(self):
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ assert p.is_running()
+ assert p.is_running()
+ p.kill()
+ p.wait()
+ assert not p.is_running()
+ assert not p.is_running()
+
+ def test_exe(self):
+ sproc = get_test_subprocess()
+ exe = psutil.Process(sproc.pid).exe()
+ try:
+ self.assertEqual(exe, PYTHON_EXE)
+ except AssertionError:
+ if WINDOWS and len(exe) == len(PYTHON_EXE):
+ # on Windows we don't care about case sensitivity
+ normcase = os.path.normcase
+ self.assertEqual(normcase(exe), normcase(PYTHON_EXE))
+ else:
+ # certain platforms such as BSD are more accurate returning:
+ # "/usr/local/bin/python2.7"
+ # ...instead of:
+ # "/usr/local/bin/python"
+ # We do not want to consider this difference in accuracy
+ # an error.
+ ver = "%s.%s" % (sys.version_info[0], sys.version_info[1])
+ try:
+ self.assertEqual(exe.replace(ver, ''),
+ PYTHON_EXE.replace(ver, ''))
+ except AssertionError:
+ # Tipically OSX. Really not sure what to do here.
+ pass
+
+ out = sh([exe, "-c", "import os; print('hey')"])
+ self.assertEqual(out, 'hey')
+
+ def test_cmdline(self):
+ cmdline = [PYTHON_EXE, "-c", "import time; time.sleep(60)"]
+ sproc = get_test_subprocess(cmdline)
+ try:
+ self.assertEqual(' '.join(psutil.Process(sproc.pid).cmdline()),
+ ' '.join(cmdline))
+ except AssertionError:
+ # XXX - most of the times the underlying sysctl() call on Net
+ # and Open BSD returns a truncated string.
+ # Also /proc/pid/cmdline behaves the same so it looks
+ # like this is a kernel bug.
+ # XXX - AIX truncates long arguments in /proc/pid/cmdline
+ if NETBSD or OPENBSD or AIX:
+ self.assertEqual(
+ psutil.Process(sproc.pid).cmdline()[0], PYTHON_EXE)
+ else:
+ raise
+
+ def test_name(self):
+ sproc = get_test_subprocess(PYTHON_EXE)
+ name = psutil.Process(sproc.pid).name().lower()
+ pyexe = os.path.basename(os.path.realpath(sys.executable)).lower()
+ assert pyexe.startswith(name), (pyexe, name)
+
+ # XXX
+ @unittest.skipIf(SUNOS, "broken on SUNOS")
+ @unittest.skipIf(AIX, "broken on AIX")
+ 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
+
+ def rm():
+ # Try to limit occasional failures on Appveyor:
+ # https://ci.appveyor.com/project/giampaolo/psutil/build/1350/
+ # job/lbo3bkju55le850n
+ try:
+ safe_rmpath(funky_path)
+ except OSError:
+ pass
+
+ funky_path = TESTFN + 'foo bar )'
+ create_exe(funky_path)
+ self.addCleanup(rm)
+ cmdline = [funky_path, "-c",
+ "import time; [time.sleep(0.01) for x in range(3000)];"
+ "arg1", "arg2", "", "arg3", ""]
+ sproc = get_test_subprocess(cmdline)
+ p = psutil.Process(sproc.pid)
+ # ...in order to try to prevent occasional failures on travis
+ if TRAVIS:
+ wait_for_pid(p.pid)
+ self.assertEqual(p.cmdline(), cmdline)
+ self.assertEqual(p.name(), os.path.basename(funky_path))
+ self.assertEqual(os.path.normcase(p.exe()),
+ os.path.normcase(funky_path))
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ def test_uids(self):
+ p = psutil.Process()
+ real, effective, saved = p.uids()
+ # os.getuid() refers to "real" uid
+ self.assertEqual(real, os.getuid())
+ # os.geteuid() refers to "effective" uid
+ self.assertEqual(effective, os.geteuid())
+ # No such thing as os.getsuid() ("saved" uid), but starting
+ # from python 2.7 we have os.getresuid() which returns all
+ # of them.
+ if hasattr(os, "getresuid"):
+ self.assertEqual(os.getresuid(), p.uids())
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ def test_gids(self):
+ p = psutil.Process()
+ real, effective, saved = p.gids()
+ # os.getuid() refers to "real" uid
+ self.assertEqual(real, os.getgid())
+ # os.geteuid() refers to "effective" uid
+ self.assertEqual(effective, os.getegid())
+ # No such thing as os.getsgid() ("saved" gid), but starting
+ # from python 2.7 we have os.getresgid() which returns all
+ # of them.
+ if hasattr(os, "getresuid"):
+ self.assertEqual(os.getresgid(), p.gids())
+
+ def test_nice(self):
+ p = psutil.Process()
+ self.assertRaises(TypeError, p.nice, "str")
+ if WINDOWS:
+ try:
+ init = p.nice()
+ if sys.version_info > (3, 4):
+ self.assertIsInstance(init, enum.IntEnum)
+ else:
+ self.assertIsInstance(init, int)
+ self.assertEqual(init, psutil.NORMAL_PRIORITY_CLASS)
+ p.nice(psutil.HIGH_PRIORITY_CLASS)
+ self.assertEqual(p.nice(), psutil.HIGH_PRIORITY_CLASS)
+ p.nice(psutil.NORMAL_PRIORITY_CLASS)
+ self.assertEqual(p.nice(), psutil.NORMAL_PRIORITY_CLASS)
+ finally:
+ p.nice(psutil.NORMAL_PRIORITY_CLASS)
+ else:
+ first_nice = p.nice()
+ try:
+ if hasattr(os, "getpriority"):
+ self.assertEqual(
+ os.getpriority(os.PRIO_PROCESS, os.getpid()), p.nice())
+ p.nice(1)
+ self.assertEqual(p.nice(), 1)
+ if hasattr(os, "getpriority"):
+ self.assertEqual(
+ os.getpriority(os.PRIO_PROCESS, os.getpid()), p.nice())
+ # XXX - going back to previous nice value raises
+ # AccessDenied on OSX
+ if not OSX:
+ p.nice(0)
+ self.assertEqual(p.nice(), 0)
+ except psutil.AccessDenied:
+ pass
+ finally:
+ try:
+ p.nice(first_nice)
+ except psutil.AccessDenied:
+ pass
+
+ def test_status(self):
+ p = psutil.Process()
+ self.assertEqual(p.status(), psutil.STATUS_RUNNING)
+
+ def test_username(self):
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ username = p.username()
+ if WINDOWS:
+ domain, username = username.split('\\')
+ self.assertEqual(username, getpass.getuser())
+ if 'USERDOMAIN' in os.environ:
+ self.assertEqual(domain, os.environ['USERDOMAIN'])
+ else:
+ self.assertEqual(username, getpass.getuser())
+
+ def test_cwd(self):
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ self.assertEqual(p.cwd(), os.getcwd())
+
+ def test_cwd_2(self):
+ cmd = [PYTHON_EXE, "-c",
+ "import os, time; os.chdir('..'); time.sleep(60)"]
+ sproc = get_test_subprocess(cmd)
+ p = psutil.Process(sproc.pid)
+ call_until(p.cwd, "ret == os.path.dirname(os.getcwd())")
+
+ @unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported')
+ def test_cpu_affinity(self):
+ p = psutil.Process()
+ initial = p.cpu_affinity()
+ assert initial, initial
+ self.addCleanup(p.cpu_affinity, initial)
+
+ if hasattr(os, "sched_getaffinity"):
+ self.assertEqual(initial, list(os.sched_getaffinity(p.pid)))
+ self.assertEqual(len(initial), len(set(initial)))
+
+ all_cpus = list(range(len(psutil.cpu_percent(percpu=True))))
+ # Work around travis failure:
+ # https://travis-ci.org/giampaolo/psutil/builds/284173194
+ for n in all_cpus if not TRAVIS else initial:
+ p.cpu_affinity([n])
+ self.assertEqual(p.cpu_affinity(), [n])
+ if hasattr(os, "sched_getaffinity"):
+ self.assertEqual(p.cpu_affinity(),
+ list(os.sched_getaffinity(p.pid)))
+ # also test num_cpu()
+ if hasattr(p, "num_cpu"):
+ self.assertEqual(p.cpu_affinity()[0], p.num_cpu())
+
+ # [] is an alias for "all eligible CPUs"; on Linux this may
+ # not be equal to all available CPUs, see:
+ # https://github.com/giampaolo/psutil/issues/956
+ p.cpu_affinity([])
+ if LINUX:
+ self.assertEqual(p.cpu_affinity(), p._proc._get_eligible_cpus())
+ else:
+ self.assertEqual(p.cpu_affinity(), all_cpus)
+ if hasattr(os, "sched_getaffinity"):
+ self.assertEqual(p.cpu_affinity(),
+ list(os.sched_getaffinity(p.pid)))
+ #
+ self.assertRaises(TypeError, p.cpu_affinity, 1)
+ p.cpu_affinity(initial)
+ # it should work with all iterables, not only lists
+ p.cpu_affinity(set(all_cpus))
+ p.cpu_affinity(tuple(all_cpus))
+
+ @unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported')
+ def test_cpu_affinity_errs(self):
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ invalid_cpu = [len(psutil.cpu_times(percpu=True)) + 10]
+ self.assertRaises(ValueError, p.cpu_affinity, invalid_cpu)
+ self.assertRaises(ValueError, p.cpu_affinity, range(10000, 11000))
+ self.assertRaises(TypeError, p.cpu_affinity, [0, "1"])
+ self.assertRaises(ValueError, p.cpu_affinity, [0, -1])
+
+ @unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported')
+ def test_cpu_affinity_all_combinations(self):
+ p = psutil.Process()
+ initial = p.cpu_affinity()
+ assert initial, initial
+ self.addCleanup(p.cpu_affinity, initial)
+
+ # All possible CPU set combinations.
+ combos = []
+ for l in range(0, len(initial) + 1):
+ for subset in itertools.combinations(initial, l):
+ if subset:
+ combos.append(list(subset))
+
+ for combo in combos:
+ p.cpu_affinity(combo)
+ self.assertEqual(p.cpu_affinity(), combo)
+
+ # TODO: #595
+ @unittest.skipIf(BSD, "broken on BSD")
+ # can't find any process file on Appveyor
+ @unittest.skipIf(APPVEYOR, "unreliable on APPVEYOR")
+ def test_open_files(self):
+ # current process
+ p = psutil.Process()
+ files = p.open_files()
+ self.assertFalse(TESTFN in files)
+ with open(TESTFN, 'wb') as f:
+ f.write(b'x' * 1024)
+ f.flush()
+ # give the kernel some time to see the new file
+ files = call_until(p.open_files, "len(ret) != %i" % len(files))
+ for file in files:
+ if file.path == TESTFN:
+ if LINUX:
+ self.assertEqual(file.position, 1024)
+ break
+ else:
+ self.fail("no file found; files=%s" % repr(files))
+ for file in files:
+ assert os.path.isfile(file.path), file
+
+ # another process
+ cmdline = "import time; f = open(r'%s', 'r'); time.sleep(60);" % TESTFN
+ sproc = get_test_subprocess([PYTHON_EXE, "-c", cmdline])
+ p = psutil.Process(sproc.pid)
+
+ for x in range(100):
+ filenames = [x.path for x in p.open_files()]
+ if TESTFN in filenames:
+ break
+ time.sleep(.01)
+ else:
+ self.assertIn(TESTFN, filenames)
+ for file in filenames:
+ assert os.path.isfile(file), file
+
+ # TODO: #595
+ @unittest.skipIf(BSD, "broken on BSD")
+ # can't find any process file on Appveyor
+ @unittest.skipIf(APPVEYOR, "unreliable on APPVEYOR")
+ def test_open_files_2(self):
+ # test fd and path fields
+ with open(TESTFN, 'w') as fileobj:
+ p = psutil.Process()
+ for file in p.open_files():
+ if file.path == fileobj.name or file.fd == fileobj.fileno():
+ break
+ else:
+ self.fail("no file found; files=%s" % repr(p.open_files()))
+ self.assertEqual(file.path, fileobj.name)
+ if WINDOWS:
+ self.assertEqual(file.fd, -1)
+ else:
+ self.assertEqual(file.fd, fileobj.fileno())
+ # test positions
+ ntuple = p.open_files()[0]
+ self.assertEqual(ntuple[0], ntuple.path)
+ self.assertEqual(ntuple[1], ntuple.fd)
+ # test file is gone
+ self.assertNotIn(fileobj.name, p.open_files())
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ def test_num_fds(self):
+ p = psutil.Process()
+ start = p.num_fds()
+ file = open(TESTFN, 'w')
+ self.addCleanup(file.close)
+ self.assertEqual(p.num_fds(), start + 1)
+ sock = socket.socket()
+ self.addCleanup(sock.close)
+ self.assertEqual(p.num_fds(), start + 2)
+ file.close()
+ sock.close()
+ self.assertEqual(p.num_fds(), start)
+
+ @skip_on_not_implemented(only_if=LINUX)
+ @unittest.skipIf(OPENBSD or NETBSD, "not reliable on OPENBSD & NETBSD")
+ def test_num_ctx_switches(self):
+ p = psutil.Process()
+ before = sum(p.num_ctx_switches())
+ for x in range(500000):
+ after = sum(p.num_ctx_switches())
+ if after > before:
+ return
+ self.fail("num ctx switches still the same after 50.000 iterations")
+
+ def test_ppid(self):
+ if hasattr(os, 'getppid'):
+ self.assertEqual(psutil.Process().ppid(), os.getppid())
+ this_parent = os.getpid()
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ self.assertEqual(p.ppid(), this_parent)
+ # no other process is supposed to have us as parent
+ reap_children(recursive=True)
+ if APPVEYOR:
+ # Occasional failures, see:
+ # https://ci.appveyor.com/project/giampaolo/psutil/build/
+ # job/0hs623nenj7w4m33
+ return
+ for p in psutil.process_iter():
+ if p.pid == sproc.pid:
+ continue
+ # XXX: sometimes this fails on Windows; not sure why.
+ self.assertNotEqual(p.ppid(), this_parent, msg=p)
+
+ def test_parent(self):
+ this_parent = os.getpid()
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ self.assertEqual(p.parent().pid, this_parent)
+
+ def test_parent_disappeared(self):
+ # Emulate a case where the parent process disappeared.
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ with mock.patch("psutil.Process",
+ side_effect=psutil.NoSuchProcess(0, 'foo')):
+ self.assertIsNone(p.parent())
+
+ def test_children(self):
+ p = psutil.Process()
+ self.assertEqual(p.children(), [])
+ self.assertEqual(p.children(recursive=True), [])
+ # On Windows we set the flag to 0 in order to cancel out the
+ # CREATE_NO_WINDOW flag (enabled by default) which creates
+ # an extra "conhost.exe" child.
+ sproc = get_test_subprocess(creationflags=0)
+ children1 = p.children()
+ children2 = p.children(recursive=True)
+ for children in (children1, children2):
+ self.assertEqual(len(children), 1)
+ self.assertEqual(children[0].pid, sproc.pid)
+ self.assertEqual(children[0].ppid(), os.getpid())
+
+ def test_children_recursive(self):
+ # Test children() against two sub processes, p1 and p2, where
+ # p1 (our child) spawned p2 (our grandchild).
+ p1, p2 = create_proc_children_pair()
+ p = psutil.Process()
+ self.assertEqual(p.children(), [p1])
+ self.assertEqual(p.children(recursive=True), [p1, p2])
+ # If the intermediate process is gone there's no way for
+ # children() to recursively find it.
+ p1.terminate()
+ p1.wait()
+ self.assertEqual(p.children(recursive=True), [])
+
+ def test_children_duplicates(self):
+ # find the process which has the highest number of children
+ table = collections.defaultdict(int)
+ for p in psutil.process_iter():
+ try:
+ table[p.ppid()] += 1
+ except psutil.Error:
+ pass
+ # this is the one, now let's make sure there are no duplicates
+ pid = sorted(table.items(), key=lambda x: x[1])[-1][0]
+ p = psutil.Process(pid)
+ try:
+ c = p.children(recursive=True)
+ except psutil.AccessDenied: # windows
+ pass
+ else:
+ self.assertEqual(len(c), len(set(c)))
+
+ def test_suspend_resume(self):
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ p.suspend()
+ for x in range(100):
+ if p.status() == psutil.STATUS_STOPPED:
+ break
+ time.sleep(0.01)
+ p.resume()
+ self.assertNotEqual(p.status(), psutil.STATUS_STOPPED)
+
+ def test_invalid_pid(self):
+ self.assertRaises(TypeError, psutil.Process, "1")
+ self.assertRaises(ValueError, psutil.Process, -1)
+
+ def test_as_dict(self):
+ p = psutil.Process()
+ d = p.as_dict(attrs=['exe', 'name'])
+ self.assertEqual(sorted(d.keys()), ['exe', 'name'])
+
+ p = psutil.Process(min(psutil.pids()))
+ d = p.as_dict(attrs=['connections'], ad_value='foo')
+ if not isinstance(d['connections'], list):
+ self.assertEqual(d['connections'], 'foo')
+
+ # Test ad_value is set on AccessDenied.
+ with mock.patch('psutil.Process.nice', create=True,
+ side_effect=psutil.AccessDenied):
+ self.assertEqual(
+ p.as_dict(attrs=["nice"], ad_value=1), {"nice": 1})
+
+ # Test that NoSuchProcess bubbles up.
+ with mock.patch('psutil.Process.nice', create=True,
+ side_effect=psutil.NoSuchProcess(p.pid, "name")):
+ self.assertRaises(
+ psutil.NoSuchProcess, p.as_dict, attrs=["nice"])
+
+ # Test that ZombieProcess is swallowed.
+ with mock.patch('psutil.Process.nice', create=True,
+ side_effect=psutil.ZombieProcess(p.pid, "name")):
+ self.assertEqual(
+ p.as_dict(attrs=["nice"], ad_value="foo"), {"nice": "foo"})
+
+ # By default APIs raising NotImplementedError are
+ # supposed to be skipped.
+ with mock.patch('psutil.Process.nice', create=True,
+ side_effect=NotImplementedError):
+ d = p.as_dict()
+ self.assertNotIn('nice', list(d.keys()))
+ # ...unless the user explicitly asked for some attr.
+ with self.assertRaises(NotImplementedError):
+ p.as_dict(attrs=["nice"])
+
+ # errors
+ with self.assertRaises(TypeError):
+ p.as_dict('name')
+ with self.assertRaises(ValueError):
+ p.as_dict(['foo'])
+ with self.assertRaises(ValueError):
+ p.as_dict(['foo', 'bar'])
+
+ def test_oneshot(self):
+ with mock.patch("psutil._psplatform.Process.cpu_times") as m:
+ p = psutil.Process()
+ with p.oneshot():
+ p.cpu_times()
+ p.cpu_times()
+ self.assertEqual(m.call_count, 1)
+
+ with mock.patch("psutil._psplatform.Process.cpu_times") as m:
+ p.cpu_times()
+ p.cpu_times()
+ self.assertEqual(m.call_count, 2)
+
+ def test_oneshot_twice(self):
+ # Test the case where the ctx manager is __enter__ed twice.
+ # The second __enter__ is supposed to resut in a NOOP.
+ with mock.patch("psutil._psplatform.Process.cpu_times") as m1:
+ with mock.patch("psutil._psplatform.Process.oneshot_enter") as m2:
+ p = psutil.Process()
+ with p.oneshot():
+ p.cpu_times()
+ p.cpu_times()
+ with p.oneshot():
+ p.cpu_times()
+ p.cpu_times()
+ self.assertEqual(m1.call_count, 1)
+ self.assertEqual(m2.call_count, 1)
+
+ with mock.patch("psutil._psplatform.Process.cpu_times") as m:
+ p.cpu_times()
+ p.cpu_times()
+ self.assertEqual(m.call_count, 2)
+
+ def test_halfway_terminated_process(self):
+ # Test that NoSuchProcess exception gets raised in case the
+ # process dies after we create the Process object.
+ # Example:
+ # >>> proc = Process(1234)
+ # >>> time.sleep(2) # time-consuming task, process dies in meantime
+ # >>> proc.name()
+ # Refers to Issue #15
+ sproc = get_test_subprocess()
+ p = psutil.Process(sproc.pid)
+ p.terminate()
+ p.wait()
+ if WINDOWS:
+ call_until(psutil.pids, "%s not in ret" % p.pid)
+ self.assertFalse(p.is_running())
+ # self.assertFalse(p.pid in psutil.pids(), msg="retcode = %s" %
+ # retcode)
+
+ excluded_names = ['pid', 'is_running', 'wait', 'create_time',
+ 'oneshot', 'memory_info_ex']
+ if LINUX and not HAS_RLIMIT:
+ excluded_names.append('rlimit')
+ for name in dir(p):
+ if (name.startswith('_') or
+ name in excluded_names):
+ continue
+ try:
+ meth = getattr(p, name)
+ # get/set methods
+ if name == 'nice':
+ if POSIX:
+ ret = meth(1)
+ else:
+ ret = meth(psutil.NORMAL_PRIORITY_CLASS)
+ elif name == 'ionice':
+ ret = meth()
+ ret = meth(2)
+ elif name == 'rlimit':
+ ret = meth(psutil.RLIMIT_NOFILE)
+ ret = meth(psutil.RLIMIT_NOFILE, (5, 5))
+ elif name == 'cpu_affinity':
+ ret = meth()
+ ret = meth([0])
+ elif name == 'send_signal':
+ ret = meth(signal.SIGTERM)
+ else:
+ ret = meth()
+ except psutil.ZombieProcess:
+ self.fail("ZombieProcess for %r was not supposed to happen" %
+ name)
+ except psutil.NoSuchProcess:
+ pass
+ except psutil.AccessDenied:
+ if OPENBSD and name in ('threads', 'num_threads'):
+ pass
+ else:
+ raise
+ except NotImplementedError:
+ pass
+ else:
+ self.fail(
+ "NoSuchProcess exception not raised for %r, retval=%s" % (
+ name, ret))
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ def test_zombie_process(self):
+ def succeed_or_zombie_p_exc(fun, *args, **kwargs):
+ try:
+ return fun(*args, **kwargs)
+ except (psutil.ZombieProcess, psutil.AccessDenied):
+ pass
+
+ zpid = create_zombie_proc()
+ self.addCleanup(reap_children, recursive=True)
+ # A zombie process should always be instantiable
+ zproc = psutil.Process(zpid)
+ # ...and at least its status always be querable
+ self.assertEqual(zproc.status(), psutil.STATUS_ZOMBIE)
+ # ...and it should be considered 'running'
+ self.assertTrue(zproc.is_running())
+ # ...and as_dict() shouldn't crash
+ zproc.as_dict()
+ # if cmdline succeeds it should be an empty list
+ ret = succeed_or_zombie_p_exc(zproc.suspend)
+ if ret is not None:
+ self.assertEqual(ret, [])
+
+ if hasattr(zproc, "rlimit"):
+ succeed_or_zombie_p_exc(zproc.rlimit, psutil.RLIMIT_NOFILE)
+ succeed_or_zombie_p_exc(zproc.rlimit, psutil.RLIMIT_NOFILE,
+ (5, 5))
+ # set methods
+ succeed_or_zombie_p_exc(zproc.parent)
+ if hasattr(zproc, 'cpu_affinity'):
+ try:
+ succeed_or_zombie_p_exc(zproc.cpu_affinity, [0])
+ except ValueError as err:
+ if TRAVIS and LINUX and "not eligible" in str(err):
+ # https://travis-ci.org/giampaolo/psutil/jobs/279890461
+ pass
+ else:
+ raise
+
+ succeed_or_zombie_p_exc(zproc.nice, 0)
+ if hasattr(zproc, 'ionice'):
+ if LINUX:
+ succeed_or_zombie_p_exc(zproc.ionice, 2, 0)
+ else:
+ succeed_or_zombie_p_exc(zproc.ionice, 0) # Windows
+ if hasattr(zproc, 'rlimit'):
+ succeed_or_zombie_p_exc(zproc.rlimit,
+ psutil.RLIMIT_NOFILE, (5, 5))
+ succeed_or_zombie_p_exc(zproc.suspend)
+ succeed_or_zombie_p_exc(zproc.resume)
+ succeed_or_zombie_p_exc(zproc.terminate)
+ succeed_or_zombie_p_exc(zproc.kill)
+
+ # ...its parent should 'see' it
+ # edit: not true on BSD and OSX
+ # descendants = [x.pid for x in psutil.Process().children(
+ # recursive=True)]
+ # self.assertIn(zpid, descendants)
+ # XXX should we also assume ppid be usable? Note: this
+ # would be an important use case as the only way to get
+ # rid of a zombie is to kill its parent.
+ # self.assertEqual(zpid.ppid(), os.getpid())
+ # ...and all other APIs should be able to deal with it
+ self.assertTrue(psutil.pid_exists(zpid))
+ if not TRAVIS and OSX:
+ # For some reason this started failing all of the sudden.
+ # Maybe they upgraded OSX version?
+ # https://travis-ci.org/giampaolo/psutil/jobs/310896404
+ self.assertIn(zpid, psutil.pids())
+ self.assertIn(zpid, [x.pid for x in psutil.process_iter()])
+ psutil._pmap = {}
+ self.assertIn(zpid, [x.pid for x in psutil.process_iter()])
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ def test_zombie_process_is_running_w_exc(self):
+ # Emulate a case where internally is_running() raises
+ # ZombieProcess.
+ p = psutil.Process()
+ with mock.patch("psutil.Process",
+ side_effect=psutil.ZombieProcess(0)) as m:
+ assert p.is_running()
+ assert m.called
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ def test_zombie_process_status_w_exc(self):
+ # Emulate a case where internally status() raises
+ # ZombieProcess.
+ p = psutil.Process()
+ with mock.patch("psutil._psplatform.Process.status",
+ side_effect=psutil.ZombieProcess(0)) as m:
+ self.assertEqual(p.status(), psutil.STATUS_ZOMBIE)
+ assert m.called
+
+ def test_pid_0(self):
+ # Process(0) is supposed to work on all platforms except Linux
+ if 0 not in psutil.pids():
+ self.assertRaises(psutil.NoSuchProcess, psutil.Process, 0)
+ return
+
+ # test all methods
+ p = psutil.Process(0)
+ for name in psutil._as_dict_attrnames:
+ if name == 'pid':
+ continue
+ meth = getattr(p, name)
+ try:
+ ret = meth()
+ except psutil.AccessDenied:
+ pass
+ else:
+ if name in ("uids", "gids"):
+ self.assertEqual(ret.real, 0)
+ elif name == "username":
+ if POSIX:
+ self.assertEqual(p.username(), 'root')
+ elif WINDOWS:
+ self.assertEqual(p.username(), 'NT AUTHORITY\\SYSTEM')
+ elif name == "name":
+ assert name, name
+
+ if hasattr(p, 'rlimit'):
+ try:
+ p.rlimit(psutil.RLIMIT_FSIZE)
+ except psutil.AccessDenied:
+ pass
+
+ p.as_dict()
+
+ if not OPENBSD:
+ self.assertIn(0, psutil.pids())
+ self.assertTrue(psutil.pid_exists(0))
+
+ @unittest.skipIf(not HAS_ENVIRON, "not supported")
+ def test_environ(self):
+ def clean_dict(d):
+ # Most of these are problematic on Travis.
+ d.pop("PSUTIL_TESTING", None)
+ d.pop("PLAT", None)
+ d.pop("HOME", None)
+ if OSX:
+ d.pop("__CF_USER_TEXT_ENCODING", None)
+ d.pop("VERSIONER_PYTHON_PREFER_32_BIT", None)
+ d.pop("VERSIONER_PYTHON_VERSION", None)
+ return dict(
+ [(k.replace("\r", "").replace("\n", ""),
+ v.replace("\r", "").replace("\n", ""))
+ for k, v in d.items()])
+
+ self.maxDiff = None
+ p = psutil.Process()
+ d1 = clean_dict(p.environ())
+ d2 = clean_dict(os.environ.copy())
+ self.assertEqual(d1, d2)
+
+ @unittest.skipIf(not HAS_ENVIRON, "not supported")
+ @unittest.skipIf(not POSIX, "POSIX only")
+ def test_weird_environ(self):
+ # environment variables can contain values without an equals sign
+ code = textwrap.dedent("""
+ #include <unistd.h>
+ #include <fcntl.h>
+ char * const argv[] = {"cat", 0};
+ char * const envp[] = {"A=1", "X", "C=3", 0};
+ int main(void) {
+ /* Close stderr on exec so parent can wait for the execve to
+ * finish. */
+ if (fcntl(2, F_SETFD, FD_CLOEXEC) != 0)
+ return 0;
+ return execve("/bin/cat", argv, envp);
+ }
+ """)
+ path = TESTFN
+ create_exe(path, c_code=code)
+ self.addCleanup(safe_rmpath, path)
+ sproc = get_test_subprocess([path],
+ stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ p = psutil.Process(sproc.pid)
+ wait_for_pid(p.pid)
+ self.assertTrue(p.is_running())
+ # Wait for process to exec or exit.
+ self.assertEqual(sproc.stderr.read(), b"")
+ self.assertEqual(p.environ(), {"A": "1", "C": "3"})
+ sproc.communicate()
+ self.assertEqual(sproc.returncode, 0)
+
+
+# ===================================================================
+# --- Limited user tests
+# ===================================================================
+
+
+if POSIX and os.getuid() == 0:
+ class LimitedUserTestCase(TestProcess):
+ """Repeat the previous tests by using a limited user.
+ Executed only on UNIX and only if the user who run the test script
+ is root.
+ """
+ # the uid/gid the test suite runs under
+ if hasattr(os, 'getuid'):
+ PROCESS_UID = os.getuid()
+ PROCESS_GID = os.getgid()
+
+ def __init__(self, *args, **kwargs):
+ TestProcess.__init__(self, *args, **kwargs)
+ # re-define all existent test methods in order to
+ # ignore AccessDenied exceptions
+ for attr in [x for x in dir(self) if x.startswith('test')]:
+ meth = getattr(self, attr)
+
+ def test_(self):
+ try:
+ meth()
+ except psutil.AccessDenied:
+ pass
+ setattr(self, attr, types.MethodType(test_, self))
+
+ def setUp(self):
+ safe_rmpath(TESTFN)
+ TestProcess.setUp(self)
+ os.setegid(1000)
+ os.seteuid(1000)
+
+ def tearDown(self):
+ os.setegid(self.PROCESS_UID)
+ os.seteuid(self.PROCESS_GID)
+ TestProcess.tearDown(self)
+
+ def test_nice(self):
+ try:
+ psutil.Process().nice(-1)
+ except psutil.AccessDenied:
+ pass
+ else:
+ self.fail("exception not raised")
+
+ def test_zombie_process(self):
+ # causes problems if test test suite is run as root
+ pass
+
+
+# ===================================================================
+# --- psutil.Popen tests
+# ===================================================================
+
+
+class TestPopen(unittest.TestCase):
+ """Tests for psutil.Popen class."""
+
+ def tearDown(self):
+ reap_children()
+
+ def test_misc(self):
+ # XXX this test causes a ResourceWarning on Python 3 because
+ # psutil.__subproc instance doesn't get propertly freed.
+ # Not sure what to do though.
+ cmd = [PYTHON_EXE, "-c", "import time; time.sleep(60);"]
+ with psutil.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE) as proc:
+ proc.name()
+ proc.cpu_times()
+ proc.stdin
+ self.assertTrue(dir(proc))
+ self.assertRaises(AttributeError, getattr, proc, 'foo')
+ proc.terminate()
+
+ def test_ctx_manager(self):
+ with psutil.Popen([PYTHON_EXE, "-V"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ stdin=subprocess.PIPE) as proc:
+ proc.communicate()
+ assert proc.stdout.closed
+ assert proc.stderr.closed
+ assert proc.stdin.closed
+ self.assertEqual(proc.returncode, 0)
+
+ def test_kill_terminate(self):
+ # subprocess.Popen()'s terminate(), kill() and send_signal() do
+ # not raise exception after the process is gone. psutil.Popen
+ # diverges from that.
+ cmd = [PYTHON_EXE, "-c", "import time; time.sleep(60);"]
+ with psutil.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE) as proc:
+ proc.terminate()
+ proc.wait()
+ self.assertRaises(psutil.NoSuchProcess, proc.terminate)
+ self.assertRaises(psutil.NoSuchProcess, proc.kill)
+ self.assertRaises(psutil.NoSuchProcess, proc.send_signal,
+ signal.SIGTERM)
+ if WINDOWS and sys.version_info >= (2, 7):
+ self.assertRaises(psutil.NoSuchProcess, proc.send_signal,
+ signal.CTRL_C_EVENT)
+ self.assertRaises(psutil.NoSuchProcess, proc.send_signal,
+ signal.CTRL_BREAK_EVENT)
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_sunos.py b/psutil/tests/test_sunos.py
new file mode 100755
index 00000000..ea9afcde
--- /dev/null
+++ b/psutil/tests/test_sunos.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Sun OS specific tests."""
+
+import os
+
+import psutil
+from psutil import SUNOS
+from psutil.tests import run_test_module_by_name
+from psutil.tests import sh
+from psutil.tests import unittest
+
+
+@unittest.skipIf(not SUNOS, "SUNOS only")
+class SunOSSpecificTestCase(unittest.TestCase):
+
+ def test_swap_memory(self):
+ out = sh('env PATH=/usr/sbin:/sbin:%s swap -l' % os.environ['PATH'])
+ lines = out.strip().split('\n')[1:]
+ if not lines:
+ raise ValueError('no swap device(s) configured')
+ total = free = 0
+ for line in lines:
+ line = line.split()
+ t, f = line[-2:]
+ total += int(int(t) * 512)
+ free += int(int(f) * 512)
+ used = total - free
+
+ psutil_swap = psutil.swap_memory()
+ self.assertEqual(psutil_swap.total, total)
+ self.assertEqual(psutil_swap.used, used)
+ self.assertEqual(psutil_swap.free, free)
+
+ def test_cpu_count(self):
+ out = sh("/usr/sbin/psrinfo")
+ self.assertEqual(psutil.cpu_count(), len(out.split('\n')))
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py
new file mode 100755
index 00000000..20b132a9
--- /dev/null
+++ b/psutil/tests/test_system.py
@@ -0,0 +1,862 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Tests for system APIS."""
+
+import contextlib
+import datetime
+import errno
+import os
+import pprint
+import shutil
+import signal
+import socket
+import sys
+import tempfile
+import time
+
+import psutil
+from psutil import AIX
+from psutil import BSD
+from psutil import FREEBSD
+from psutil import LINUX
+from psutil import NETBSD
+from psutil import OPENBSD
+from psutil import OSX
+from psutil import POSIX
+from psutil import SUNOS
+from psutil import WINDOWS
+from psutil._compat import long
+from psutil.tests import APPVEYOR
+from psutil.tests import ASCII_FS
+from psutil.tests import check_net_address
+from psutil.tests import DEVNULL
+from psutil.tests import enum
+from psutil.tests import get_test_subprocess
+from psutil.tests import HAS_BATTERY
+from psutil.tests import HAS_CPU_FREQ
+from psutil.tests import HAS_SENSORS_BATTERY
+from psutil.tests import HAS_SENSORS_FANS
+from psutil.tests import HAS_SENSORS_TEMPERATURES
+from psutil.tests import mock
+from psutil.tests import reap_children
+from psutil.tests import retry_before_failing
+from psutil.tests import run_test_module_by_name
+from psutil.tests import safe_rmpath
+from psutil.tests import TESTFN
+from psutil.tests import TESTFN_UNICODE
+from psutil.tests import TRAVIS
+from psutil.tests import unittest
+
+
+# ===================================================================
+# --- System-related API tests
+# ===================================================================
+
+
+class TestSystemAPIs(unittest.TestCase):
+ """Tests for system-related APIs."""
+
+ def setUp(self):
+ safe_rmpath(TESTFN)
+
+ def tearDown(self):
+ reap_children()
+
+ def test_process_iter(self):
+ self.assertIn(os.getpid(), [x.pid for x in psutil.process_iter()])
+ sproc = get_test_subprocess()
+ self.assertIn(sproc.pid, [x.pid for x in psutil.process_iter()])
+ p = psutil.Process(sproc.pid)
+ p.kill()
+ p.wait()
+ self.assertNotIn(sproc.pid, [x.pid for x in psutil.process_iter()])
+
+ with mock.patch('psutil.Process',
+ side_effect=psutil.NoSuchProcess(os.getpid())):
+ self.assertEqual(list(psutil.process_iter()), [])
+ with mock.patch('psutil.Process',
+ side_effect=psutil.AccessDenied(os.getpid())):
+ with self.assertRaises(psutil.AccessDenied):
+ list(psutil.process_iter())
+
+ def test_prcess_iter_w_params(self):
+ for p in psutil.process_iter(attrs=['pid']):
+ self.assertEqual(list(p.info.keys()), ['pid'])
+ with self.assertRaises(ValueError):
+ list(psutil.process_iter(attrs=['foo']))
+ with mock.patch("psutil._psplatform.Process.cpu_times",
+ side_effect=psutil.AccessDenied(0, "")) as m:
+ for p in psutil.process_iter(attrs=["pid", "cpu_times"]):
+ self.assertIsNone(p.info['cpu_times'])
+ self.assertGreaterEqual(p.info['pid'], 0)
+ assert m.called
+ with mock.patch("psutil._psplatform.Process.cpu_times",
+ side_effect=psutil.AccessDenied(0, "")) as m:
+ flag = object()
+ for p in psutil.process_iter(
+ attrs=["pid", "cpu_times"], ad_value=flag):
+ self.assertIs(p.info['cpu_times'], flag)
+ self.assertGreaterEqual(p.info['pid'], 0)
+ assert m.called
+
+ def test_wait_procs(self):
+ def callback(p):
+ pids.append(p.pid)
+
+ pids = []
+ sproc1 = get_test_subprocess()
+ sproc2 = get_test_subprocess()
+ 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)
+
+ self.assertLess(time.time() - t, 0.5)
+ self.assertEqual(gone, [])
+ self.assertEqual(len(alive), 3)
+ self.assertEqual(pids, [])
+ for p in alive:
+ self.assertFalse(hasattr(p, 'returncode'))
+
+ @retry_before_failing(30)
+ def test(procs, callback):
+ gone, alive = psutil.wait_procs(procs, timeout=0.03,
+ callback=callback)
+ self.assertEqual(len(gone), 1)
+ self.assertEqual(len(alive), 2)
+ return gone, alive
+
+ sproc3.terminate()
+ gone, alive = test(procs, callback)
+ self.assertIn(sproc3.pid, [x.pid for x in gone])
+ if POSIX:
+ self.assertEqual(gone.pop().returncode, -signal.SIGTERM)
+ else:
+ self.assertEqual(gone.pop().returncode, 1)
+ self.assertEqual(pids, [sproc3.pid])
+ for p in alive:
+ self.assertFalse(hasattr(p, 'returncode'))
+
+ @retry_before_failing(30)
+ def test(procs, callback):
+ gone, alive = psutil.wait_procs(procs, timeout=0.03,
+ callback=callback)
+ self.assertEqual(len(gone), 3)
+ self.assertEqual(len(alive), 0)
+ return gone, alive
+
+ sproc1.terminate()
+ sproc2.terminate()
+ gone, alive = test(procs, callback)
+ self.assertEqual(set(pids), set([sproc1.pid, sproc2.pid, sproc3.pid]))
+ for p in gone:
+ self.assertTrue(hasattr(p, 'returncode'))
+
+ def test_wait_procs_no_timeout(self):
+ sproc1 = get_test_subprocess()
+ sproc2 = get_test_subprocess()
+ sproc3 = get_test_subprocess()
+ procs = [psutil.Process(x.pid) for x in (sproc1, sproc2, sproc3)]
+ for p in procs:
+ p.terminate()
+ gone, alive = psutil.wait_procs(procs)
+
+ def test_boot_time(self):
+ bt = psutil.boot_time()
+ self.assertIsInstance(bt, float)
+ self.assertGreater(bt, 0)
+ self.assertLess(bt, time.time())
+
+ @unittest.skipIf(not POSIX, 'POSIX only')
+ def test_PAGESIZE(self):
+ # pagesize is used internally to perform different calculations
+ # and it's determined by using SC_PAGE_SIZE; make sure
+ # getpagesize() returns the same value.
+ import resource
+ self.assertEqual(os.sysconf("SC_PAGE_SIZE"), resource.getpagesize())
+
+ def test_virtual_memory(self):
+ mem = psutil.virtual_memory()
+ assert mem.total > 0, mem
+ assert mem.available > 0, mem
+ assert 0 <= mem.percent <= 100, mem
+ assert mem.used > 0, mem
+ assert mem.free >= 0, mem
+ for name in mem._fields:
+ value = getattr(mem, name)
+ if name != 'percent':
+ self.assertIsInstance(value, (int, long))
+ if name != 'total':
+ if not value >= 0:
+ self.fail("%r < 0 (%s)" % (name, value))
+ if value > mem.total:
+ self.fail("%r > total (total=%s, %s=%s)"
+ % (name, mem.total, name, value))
+
+ def test_swap_memory(self):
+ mem = psutil.swap_memory()
+ self.assertEqual(
+ mem._fields, ('total', 'used', 'free', 'percent', 'sin', 'sout'))
+
+ assert mem.total >= 0, mem
+ assert mem.used >= 0, mem
+ if mem.total > 0:
+ # likely a system with no swap partition
+ assert mem.free > 0, mem
+ else:
+ assert mem.free == 0, mem
+ assert 0 <= mem.percent <= 100, mem
+ assert mem.sin >= 0, mem
+ assert mem.sout >= 0, mem
+
+ def test_pid_exists(self):
+ sproc = get_test_subprocess()
+ self.assertTrue(psutil.pid_exists(sproc.pid))
+ p = psutil.Process(sproc.pid)
+ p.kill()
+ p.wait()
+ self.assertFalse(psutil.pid_exists(sproc.pid))
+ self.assertFalse(psutil.pid_exists(-1))
+ self.assertEqual(psutil.pid_exists(0), 0 in psutil.pids())
+
+ def test_pid_exists_2(self):
+ reap_children()
+ pids = psutil.pids()
+ for pid in pids:
+ try:
+ assert psutil.pid_exists(pid)
+ except AssertionError:
+ # in case the process disappeared in meantime fail only
+ # if it is no longer in psutil.pids()
+ time.sleep(.1)
+ if pid in psutil.pids():
+ self.fail(pid)
+ pids = range(max(pids) + 5000, max(pids) + 6000)
+ for pid in pids:
+ self.assertFalse(psutil.pid_exists(pid), msg=pid)
+
+ def test_pids(self):
+ plist = [x.pid for x in psutil.process_iter()]
+ pidlist = psutil.pids()
+ self.assertEqual(plist.sort(), pidlist.sort())
+ # make sure every pid is unique
+ self.assertEqual(len(pidlist), len(set(pidlist)))
+
+ def test_test(self):
+ # test for psutil.test() function
+ stdout = sys.stdout
+ sys.stdout = DEVNULL
+ try:
+ psutil.test()
+ finally:
+ sys.stdout = stdout
+
+ def test_cpu_count(self):
+ logical = psutil.cpu_count()
+ self.assertEqual(logical, len(psutil.cpu_times(percpu=True)))
+ self.assertGreaterEqual(logical, 1)
+ #
+ if os.path.exists("/proc/cpuinfo"):
+ 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)
+
+ def test_cpu_count_none(self):
+ # https://github.com/giampaolo/psutil/issues/1085
+ for val in (-1, 0, None):
+ with mock.patch('psutil._psplatform.cpu_count_logical',
+ return_value=val) as m:
+ self.assertIsNone(psutil.cpu_count())
+ assert m.called
+ with mock.patch('psutil._psplatform.cpu_count_physical',
+ return_value=val) as m:
+ self.assertIsNone(psutil.cpu_count(logical=False))
+ assert m.called
+
+ def test_cpu_times(self):
+ # Check type, value >= 0, str().
+ total = 0
+ times = psutil.cpu_times()
+ sum(times)
+ for cp_time in times:
+ self.assertIsInstance(cp_time, float)
+ self.assertGreaterEqual(cp_time, 0.0)
+ total += cp_time
+ self.assertEqual(total, sum(times))
+ str(times)
+ # CPU times are always supposed to increase over time
+ # or at least remain the same and that's because time
+ # cannot go backwards.
+ # Surprisingly sometimes this might not be the case (at
+ # least on Windows and Linux), see:
+ # https://github.com/giampaolo/psutil/issues/392
+ # https://github.com/giampaolo/psutil/issues/645
+ # if not WINDOWS:
+ # last = psutil.cpu_times()
+ # for x in range(100):
+ # new = psutil.cpu_times()
+ # for field in new._fields:
+ # new_t = getattr(new, field)
+ # last_t = getattr(last, field)
+ # self.assertGreaterEqual(new_t, last_t,
+ # msg="%s %s" % (new_t, last_t))
+ # last = new
+
+ def test_cpu_times_time_increases(self):
+ # Make sure time increases between calls.
+ t1 = sum(psutil.cpu_times())
+ time.sleep(0.1)
+ t2 = sum(psutil.cpu_times())
+ difference = t2 - t1
+ if not difference >= 0.05:
+ self.fail("difference %s" % difference)
+
+ def test_per_cpu_times(self):
+ # Check type, value >= 0, str().
+ for times in psutil.cpu_times(percpu=True):
+ total = 0
+ sum(times)
+ for cp_time in times:
+ self.assertIsInstance(cp_time, float)
+ self.assertGreaterEqual(cp_time, 0.0)
+ total += cp_time
+ self.assertEqual(total, sum(times))
+ str(times)
+ self.assertEqual(len(psutil.cpu_times(percpu=True)[0]),
+ len(psutil.cpu_times(percpu=False)))
+
+ # Note: in theory CPU times are always supposed to increase over
+ # time or remain the same but never go backwards. In practice
+ # sometimes this is not the case.
+ # This issue seemd to be afflict Windows:
+ # https://github.com/giampaolo/psutil/issues/392
+ # ...but it turns out also Linux (rarely) behaves the same.
+ # last = psutil.cpu_times(percpu=True)
+ # for x in range(100):
+ # new = psutil.cpu_times(percpu=True)
+ # for index in range(len(new)):
+ # newcpu = new[index]
+ # lastcpu = last[index]
+ # for field in newcpu._fields:
+ # new_t = getattr(newcpu, field)
+ # last_t = getattr(lastcpu, field)
+ # self.assertGreaterEqual(
+ # new_t, last_t, msg="%s %s" % (lastcpu, newcpu))
+ # last = new
+
+ def test_per_cpu_times_2(self):
+ # Simulate some work load then make sure time have increased
+ # between calls.
+ tot1 = psutil.cpu_times(percpu=True)
+ stop_at = time.time() + 0.1
+ while True:
+ if time.time() >= stop_at:
+ break
+ tot2 = psutil.cpu_times(percpu=True)
+ for t1, t2 in zip(tot1, tot2):
+ t1, t2 = sum(t1), sum(t2)
+ difference = t2 - t1
+ if difference >= 0.05:
+ return
+ self.fail()
+
+ def test_cpu_times_comparison(self):
+ # Make sure the sum of all per cpu times is almost equal to
+ # base "one cpu" times.
+ base = psutil.cpu_times()
+ per_cpu = psutil.cpu_times(percpu=True)
+ summed_values = base._make([sum(num) for num in zip(*per_cpu)])
+ for field in base._fields:
+ self.assertAlmostEqual(
+ getattr(base, field), getattr(summed_values, field), delta=1)
+
+ def _test_cpu_percent(self, percent, last_ret, new_ret):
+ try:
+ self.assertIsInstance(percent, float)
+ self.assertGreaterEqual(percent, 0.0)
+ self.assertIsNot(percent, -0.0)
+ self.assertLessEqual(percent, 100.0 * psutil.cpu_count())
+ except AssertionError as err:
+ raise AssertionError("\n%s\nlast=%s\nnew=%s" % (
+ err, pprint.pformat(last_ret), pprint.pformat(new_ret)))
+
+ def test_cpu_percent(self):
+ last = psutil.cpu_percent(interval=0.001)
+ for x in range(100):
+ new = psutil.cpu_percent(interval=None)
+ self._test_cpu_percent(new, last, new)
+ last = new
+ with self.assertRaises(ValueError):
+ psutil.cpu_percent(interval=-1)
+
+ def test_per_cpu_percent(self):
+ last = psutil.cpu_percent(interval=0.001, percpu=True)
+ self.assertEqual(len(last), psutil.cpu_count())
+ for x in range(100):
+ new = psutil.cpu_percent(interval=None, percpu=True)
+ for percent in new:
+ self._test_cpu_percent(percent, last, new)
+ last = new
+ with self.assertRaises(ValueError):
+ psutil.cpu_percent(interval=-1, percpu=True)
+
+ def test_cpu_times_percent(self):
+ last = psutil.cpu_times_percent(interval=0.001)
+ for x in range(100):
+ new = psutil.cpu_times_percent(interval=None)
+ for percent in new:
+ self._test_cpu_percent(percent, last, new)
+ self._test_cpu_percent(sum(new), last, new)
+ last = new
+
+ def test_per_cpu_times_percent(self):
+ last = psutil.cpu_times_percent(interval=0.001, percpu=True)
+ self.assertEqual(len(last), psutil.cpu_count())
+ for x in range(100):
+ new = psutil.cpu_times_percent(interval=None, percpu=True)
+ for cpu in new:
+ for percent in cpu:
+ self._test_cpu_percent(percent, last, new)
+ self._test_cpu_percent(sum(cpu), last, new)
+ last = new
+
+ def test_per_cpu_times_percent_negative(self):
+ # see: https://github.com/giampaolo/psutil/issues/645
+ psutil.cpu_times_percent(percpu=True)
+ zero_times = [x._make([0 for x in range(len(x._fields))])
+ for x in psutil.cpu_times(percpu=True)]
+ with mock.patch('psutil.cpu_times', return_value=zero_times):
+ for cpu in psutil.cpu_times_percent(percpu=True):
+ for percent in cpu:
+ self._test_cpu_percent(percent, None, None)
+
+ def test_disk_usage(self):
+ usage = psutil.disk_usage(os.getcwd())
+ self.assertEqual(usage._fields, ('total', 'used', 'free', 'percent'))
+
+ assert usage.total > 0, usage
+ assert usage.used > 0, usage
+ assert usage.free > 0, usage
+ assert usage.total > usage.used, usage
+ assert usage.total > usage.free, usage
+ assert 0 <= usage.percent <= 100, usage.percent
+ if hasattr(shutil, 'disk_usage'):
+ # py >= 3.3, see: http://bugs.python.org/issue12442
+ shutil_usage = shutil.disk_usage(os.getcwd())
+ tolerance = 5 * 1024 * 1024 # 5MB
+ self.assertEqual(usage.total, shutil_usage.total)
+ self.assertAlmostEqual(usage.free, shutil_usage.free,
+ delta=tolerance)
+ self.assertAlmostEqual(usage.used, shutil_usage.used,
+ delta=tolerance)
+
+ # if path does not exist OSError ENOENT is expected across
+ # all platforms
+ fname = tempfile.mktemp()
+ with self.assertRaises(OSError) as exc:
+ psutil.disk_usage(fname)
+ self.assertEqual(exc.exception.errno, errno.ENOENT)
+
+ def test_disk_usage_unicode(self):
+ # See: https://github.com/giampaolo/psutil/issues/416
+ if ASCII_FS:
+ with self.assertRaises(UnicodeEncodeError):
+ psutil.disk_usage(TESTFN_UNICODE)
+
+ def test_disk_usage_bytes(self):
+ psutil.disk_usage(b'.')
+
+ def test_disk_partitions(self):
+ # all = False
+ ls = psutil.disk_partitions(all=False)
+ # on travis we get:
+ # self.assertEqual(p.cpu_affinity(), [n])
+ # AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7,... != [0]
+ self.assertTrue(ls, msg=ls)
+ for disk in ls:
+ self.assertIsInstance(disk.device, str)
+ self.assertIsInstance(disk.mountpoint, str)
+ self.assertIsInstance(disk.fstype, str)
+ self.assertIsInstance(disk.opts, str)
+ if WINDOWS and 'cdrom' in disk.opts:
+ continue
+ if not POSIX:
+ assert os.path.exists(disk.device), disk
+ else:
+ # we cannot make any assumption about this, see:
+ # http://goo.gl/p9c43
+ disk.device
+ if SUNOS or TRAVIS:
+ # on solaris apparently mount points can also be files
+ assert os.path.exists(disk.mountpoint), disk
+ else:
+ assert os.path.isdir(disk.mountpoint), disk
+ assert disk.fstype, disk
+
+ # all = True
+ ls = psutil.disk_partitions(all=True)
+ self.assertTrue(ls, msg=ls)
+ for disk in psutil.disk_partitions(all=True):
+ if not WINDOWS:
+ try:
+ os.stat(disk.mountpoint)
+ except OSError as err:
+ if TRAVIS and OSX and err.errno == errno.EIO:
+ continue
+ # http://mail.python.org/pipermail/python-dev/
+ # 2012-June/120787.html
+ if err.errno not in (errno.EPERM, errno.EACCES):
+ raise
+ else:
+ if SUNOS or TRAVIS:
+ # on solaris apparently mount points can also be files
+ assert os.path.exists(disk.mountpoint), disk
+ else:
+ assert os.path.isdir(disk.mountpoint), disk
+ self.assertIsInstance(disk.fstype, str)
+ self.assertIsInstance(disk.opts, str)
+
+ def find_mount_point(path):
+ path = os.path.abspath(path)
+ while not os.path.ismount(path):
+ path = os.path.dirname(path)
+ return path.lower()
+
+ mount = find_mount_point(__file__)
+ mounts = [x.mountpoint.lower() for x in
+ psutil.disk_partitions(all=True)]
+ self.assertIn(mount, mounts)
+ psutil.disk_usage(mount)
+
+ def test_net_io_counters(self):
+ def check_ntuple(nt):
+ self.assertEqual(nt[0], nt.bytes_sent)
+ self.assertEqual(nt[1], nt.bytes_recv)
+ self.assertEqual(nt[2], nt.packets_sent)
+ self.assertEqual(nt[3], nt.packets_recv)
+ self.assertEqual(nt[4], nt.errin)
+ self.assertEqual(nt[5], nt.errout)
+ self.assertEqual(nt[6], nt.dropin)
+ self.assertEqual(nt[7], nt.dropout)
+ assert nt.bytes_sent >= 0, nt
+ assert nt.bytes_recv >= 0, nt
+ assert nt.packets_sent >= 0, nt
+ assert nt.packets_recv >= 0, nt
+ assert nt.errin >= 0, nt
+ assert nt.errout >= 0, nt
+ assert nt.dropin >= 0, nt
+ assert nt.dropout >= 0, nt
+
+ ret = psutil.net_io_counters(pernic=False)
+ check_ntuple(ret)
+ ret = psutil.net_io_counters(pernic=True)
+ self.assertNotEqual(ret, [])
+ for key in ret:
+ self.assertTrue(key)
+ self.assertIsInstance(key, str)
+ check_ntuple(ret[key])
+
+ def test_net_io_counters_no_nics(self):
+ # Emulate a case where no NICs are installed, see:
+ # https://github.com/giampaolo/psutil/issues/1062
+ with mock.patch('psutil._psplatform.net_io_counters',
+ return_value={}) as m:
+ self.assertIsNone(psutil.net_io_counters(pernic=False))
+ self.assertEqual(psutil.net_io_counters(pernic=True), {})
+ assert m.called
+
+ def test_net_if_addrs(self):
+ nics = psutil.net_if_addrs()
+ assert nics, nics
+
+ nic_stats = psutil.net_if_stats()
+
+ # Not reliable on all platforms (net_if_addrs() reports more
+ # interfaces).
+ # self.assertEqual(sorted(nics.keys()),
+ # sorted(psutil.net_io_counters(pernic=True).keys()))
+
+ families = set([socket.AF_INET, socket.AF_INET6, psutil.AF_LINK])
+ for nic, addrs in nics.items():
+ self.assertIsInstance(nic, str)
+ self.assertEqual(len(set(addrs)), len(addrs))
+ for addr in addrs:
+ self.assertIsInstance(addr.family, int)
+ self.assertIsInstance(addr.address, str)
+ self.assertIsInstance(addr.netmask, (str, type(None)))
+ self.assertIsInstance(addr.broadcast, (str, type(None)))
+ self.assertIn(addr.family, families)
+ if sys.version_info >= (3, 4):
+ self.assertIsInstance(addr.family, enum.IntEnum)
+ if nic_stats[nic].isup:
+ # Do not test binding to addresses of interfaces
+ # that are down
+ if addr.family == socket.AF_INET:
+ s = socket.socket(addr.family)
+ with contextlib.closing(s):
+ s.bind((addr.address, 0))
+ elif addr.family == socket.AF_INET6:
+ info = socket.getaddrinfo(
+ addr.address, 0, socket.AF_INET6,
+ socket.SOCK_STREAM, 0, socket.AI_PASSIVE)[0]
+ af, socktype, proto, canonname, sa = info
+ s = socket.socket(af, socktype, proto)
+ with contextlib.closing(s):
+ s.bind(sa)
+ for ip in (addr.address, addr.netmask, addr.broadcast,
+ addr.ptp):
+ if ip is not None:
+ # TODO: skip AF_INET6 for now because I get:
+ # AddressValueError: Only hex digits permitted in
+ # u'c6f3%lxcbr0' in u'fe80::c8e0:fff:fe54:c6f3%lxcbr0'
+ if addr.family != socket.AF_INET6:
+ check_net_address(ip, addr.family)
+ # broadcast and ptp addresses are mutually exclusive
+ if addr.broadcast:
+ self.assertIsNone(addr.ptp)
+ elif addr.ptp:
+ self.assertIsNone(addr.broadcast)
+
+ if BSD or OSX or SUNOS:
+ if hasattr(socket, "AF_LINK"):
+ self.assertEqual(psutil.AF_LINK, socket.AF_LINK)
+ elif LINUX:
+ self.assertEqual(psutil.AF_LINK, socket.AF_PACKET)
+ elif WINDOWS:
+ self.assertEqual(psutil.AF_LINK, -1)
+
+ def test_net_if_addrs_mac_null_bytes(self):
+ # Simulate that the underlying C function returns an incomplete
+ # MAC address. psutil is supposed to fill it with null bytes.
+ # https://github.com/giampaolo/psutil/issues/786
+ if POSIX:
+ ret = [('em1', psutil.AF_LINK, '06:3d:29', None, None, None)]
+ else:
+ ret = [('em1', -1, '06-3d-29', None, None, None)]
+ with mock.patch('psutil._psplatform.net_if_addrs',
+ return_value=ret) as m:
+ addr = psutil.net_if_addrs()['em1'][0]
+ assert m.called
+ if POSIX:
+ self.assertEqual(addr.address, '06:3d:29:00:00:00')
+ else:
+ self.assertEqual(addr.address, '06-3d-29-00-00-00')
+
+ @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") # raises EPERM
+ def test_net_if_stats(self):
+ nics = psutil.net_if_stats()
+ assert nics, nics
+ all_duplexes = (psutil.NIC_DUPLEX_FULL,
+ psutil.NIC_DUPLEX_HALF,
+ psutil.NIC_DUPLEX_UNKNOWN)
+ for name, stats in nics.items():
+ self.assertIsInstance(name, str)
+ isup, duplex, speed, mtu = stats
+ self.assertIsInstance(isup, bool)
+ self.assertIn(duplex, all_duplexes)
+ self.assertIn(duplex, all_duplexes)
+ self.assertGreaterEqual(speed, 0)
+ self.assertGreaterEqual(mtu, 0)
+
+ @unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'),
+ '/proc/diskstats not available on this linux version')
+ @unittest.skipIf(APPVEYOR and psutil.disk_io_counters() is None,
+ "unreliable on APPVEYOR") # no visible disks
+ def test_disk_io_counters(self):
+ def check_ntuple(nt):
+ self.assertEqual(nt[0], nt.read_count)
+ self.assertEqual(nt[1], nt.write_count)
+ self.assertEqual(nt[2], nt.read_bytes)
+ self.assertEqual(nt[3], nt.write_bytes)
+ if not (OPENBSD or NETBSD):
+ self.assertEqual(nt[4], nt.read_time)
+ self.assertEqual(nt[5], nt.write_time)
+ if LINUX:
+ self.assertEqual(nt[6], nt.read_merged_count)
+ self.assertEqual(nt[7], nt.write_merged_count)
+ self.assertEqual(nt[8], nt.busy_time)
+ elif FREEBSD:
+ self.assertEqual(nt[6], nt.busy_time)
+ for name in nt._fields:
+ assert getattr(nt, name) >= 0, nt
+
+ ret = psutil.disk_io_counters(perdisk=False)
+ assert ret is not None, "no disks on this system?"
+ check_ntuple(ret)
+ ret = psutil.disk_io_counters(perdisk=True)
+ # make sure there are no duplicates
+ self.assertEqual(len(ret), len(set(ret)))
+ for key in ret:
+ assert key, key
+ check_ntuple(ret[key])
+ if LINUX and key[-1].isdigit():
+ # if 'sda1' is listed 'sda' shouldn't, see:
+ # https://github.com/giampaolo/psutil/issues/338
+ while key[-1].isdigit():
+ key = key[:-1]
+ self.assertNotIn(key, ret.keys())
+
+ def test_disk_io_counters_no_disks(self):
+ # Emulate a case where no disks are installed, see:
+ # https://github.com/giampaolo/psutil/issues/1062
+ with mock.patch('psutil._psplatform.disk_io_counters',
+ return_value={}) as m:
+ self.assertIsNone(psutil.disk_io_counters(perdisk=False))
+ self.assertEqual(psutil.disk_io_counters(perdisk=True), {})
+ assert m.called
+
+ # can't find users on APPVEYOR or TRAVIS
+ @unittest.skipIf(APPVEYOR or TRAVIS and not psutil.users(),
+ "unreliable on APPVEYOR or TRAVIS")
+ def test_users(self):
+ users = psutil.users()
+ self.assertNotEqual(users, [])
+ for user in users:
+ assert user.name, user
+ self.assertIsInstance(user.name, str)
+ self.assertIsInstance(user.terminal, (str, type(None)))
+ if user.host is not None:
+ self.assertIsInstance(user.host, (str, type(None)))
+ user.terminal
+ user.host
+ assert user.started > 0.0, user
+ datetime.datetime.fromtimestamp(user.started)
+ if WINDOWS or OPENBSD:
+ self.assertIsNone(user.pid)
+ else:
+ psutil.Process(user.pid)
+
+ def test_cpu_stats(self):
+ # Tested more extensively in per-platform test modules.
+ infos = psutil.cpu_stats()
+ self.assertEqual(
+ infos._fields,
+ ('ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'))
+ for name in infos._fields:
+ value = getattr(infos, name)
+ self.assertGreaterEqual(value, 0)
+ # on AIX, ctx_switches is always 0
+ if not AIX and name in ('ctx_switches', 'interrupts'):
+ self.assertGreater(value, 0)
+
+ @unittest.skipIf(not HAS_CPU_FREQ, "not suported")
+ def test_cpu_freq(self):
+ def check_ls(ls):
+ for nt in ls:
+ self.assertEqual(nt._fields, ('current', 'min', 'max'))
+ self.assertLessEqual(nt.current, nt.max)
+ for name in nt._fields:
+ value = getattr(nt, name)
+ self.assertIsInstance(value, (int, long, float))
+ self.assertGreaterEqual(value, 0)
+
+ ls = psutil.cpu_freq(percpu=True)
+ if TRAVIS and not ls:
+ return
+
+ assert ls, ls
+ check_ls([psutil.cpu_freq(percpu=False)])
+
+ if LINUX:
+ self.assertEqual(len(ls), psutil.cpu_count())
+
+ def test_os_constants(self):
+ names = ["POSIX", "WINDOWS", "LINUX", "OSX", "FREEBSD", "OPENBSD",
+ "NETBSD", "BSD", "SUNOS"]
+ for name in names:
+ self.assertIsInstance(getattr(psutil, name), bool, msg=name)
+
+ if os.name == 'posix':
+ assert psutil.POSIX
+ assert not psutil.WINDOWS
+ names.remove("POSIX")
+ if "linux" in sys.platform.lower():
+ assert psutil.LINUX
+ names.remove("LINUX")
+ elif "bsd" in sys.platform.lower():
+ assert psutil.BSD
+ self.assertEqual([psutil.FREEBSD, psutil.OPENBSD,
+ psutil.NETBSD].count(True), 1)
+ names.remove("BSD")
+ names.remove("FREEBSD")
+ names.remove("OPENBSD")
+ names.remove("NETBSD")
+ elif "sunos" in sys.platform.lower() or \
+ "solaris" in sys.platform.lower():
+ assert psutil.SUNOS
+ names.remove("SUNOS")
+ elif "darwin" in sys.platform.lower():
+ assert psutil.OSX
+ names.remove("OSX")
+ else:
+ assert psutil.WINDOWS
+ assert not psutil.POSIX
+ names.remove("WINDOWS")
+
+ # assert all other constants are set to False
+ for name in names:
+ self.assertIs(getattr(psutil, name), False, msg=name)
+
+ @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported")
+ def test_sensors_temperatures(self):
+ temps = psutil.sensors_temperatures()
+ for name, entries in temps.items():
+ self.assertIsInstance(name, str)
+ for entry in entries:
+ self.assertIsInstance(entry.label, str)
+ if entry.current is not None:
+ self.assertGreaterEqual(entry.current, 0)
+ if entry.high is not None:
+ self.assertGreaterEqual(entry.high, 0)
+ if entry.critical is not None:
+ self.assertGreaterEqual(entry.critical, 0)
+
+ @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported")
+ def test_sensors_temperatures_fahreneit(self):
+ d = {'coretemp': [('label', 50.0, 60.0, 70.0)]}
+ with mock.patch("psutil._psplatform.sensors_temperatures",
+ return_value=d) as m:
+ temps = psutil.sensors_temperatures(
+ fahrenheit=True)['coretemp'][0]
+ assert m.called
+ self.assertEqual(temps.current, 122.0)
+ self.assertEqual(temps.high, 140.0)
+ self.assertEqual(temps.critical, 158.0)
+
+ @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported")
+ @unittest.skipIf(not HAS_BATTERY, "no battery")
+ def test_sensors_battery(self):
+ ret = psutil.sensors_battery()
+ self.assertGreaterEqual(ret.percent, 0)
+ self.assertLessEqual(ret.percent, 100)
+ if ret.secsleft not in (psutil.POWER_TIME_UNKNOWN,
+ psutil.POWER_TIME_UNLIMITED):
+ self.assertGreaterEqual(ret.secsleft, 0)
+ else:
+ if ret.secsleft == psutil.POWER_TIME_UNLIMITED:
+ self.assertTrue(ret.power_plugged)
+ self.assertIsInstance(ret.power_plugged, bool)
+
+ @unittest.skipIf(not HAS_SENSORS_FANS, "not supported")
+ def test_sensors_fans(self):
+ fans = psutil.sensors_fans()
+ for name, entries in fans.items():
+ self.assertIsInstance(name, str)
+ for entry in entries:
+ self.assertIsInstance(entry.label, str)
+ self.assertIsInstance(entry.current, (int, long))
+ self.assertGreaterEqual(entry.current, 0)
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py
new file mode 100755
index 00000000..6383c9be
--- /dev/null
+++ b/psutil/tests/test_unicode.py
@@ -0,0 +1,366 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Notes about unicode handling in psutil
+======================================
+
+In psutil these are the APIs returning or dealing with a string
+('not tested' means they are not tested to deal with non-ASCII strings):
+
+* Process.cmdline()
+* Process.connections('unix')
+* Process.cwd()
+* Process.environ()
+* Process.exe()
+* Process.memory_maps()
+* Process.name()
+* Process.open_files()
+* Process.username() (not tested)
+
+* disk_io_counters() (not tested)
+* disk_partitions() (not tested)
+* disk_usage(str)
+* net_connections('unix')
+* net_if_addrs() (not tested)
+* net_if_stats() (not tested)
+* net_io_counters() (not tested)
+* sensors_fans() (not tested)
+* sensors_temperatures() (not tested)
+* users() (not tested)
+
+* WindowsService.binpath() (not tested)
+* WindowsService.description() (not tested)
+* WindowsService.display_name() (not tested)
+* WindowsService.name() (not tested)
+* WindowsService.status() (not tested)
+* WindowsService.username() (not tested)
+
+In here we create a unicode path with a funky non-ASCII name and (where
+possible) make psutil return it back (e.g. on name(), exe(), open_files(),
+etc.) and make sure that:
+
+* psutil never crashes with UnicodeDecodeError
+* the returned path matches
+
+For a detailed explanation of how psutil handles unicode see:
+- https://github.com/giampaolo/psutil/issues/1040
+- http://psutil.readthedocs.io/#unicode
+"""
+
+import os
+import traceback
+import warnings
+from contextlib import closing
+
+from psutil import BSD
+from psutil import OPENBSD
+from psutil import OSX
+from psutil import POSIX
+from psutil import WINDOWS
+from psutil._compat import PY3
+from psutil._compat import u
+from psutil.tests import APPVEYOR
+from psutil.tests import ASCII_FS
+from psutil.tests import bind_unix_socket
+from psutil.tests import chdir
+from psutil.tests import copyload_shared_lib
+from psutil.tests import create_exe
+from psutil.tests import get_test_subprocess
+from psutil.tests import HAS_CONNECTIONS_UNIX
+from psutil.tests import HAS_ENVIRON
+from psutil.tests import HAS_MEMORY_MAPS
+from psutil.tests import mock
+from psutil.tests import reap_children
+from psutil.tests import run_test_module_by_name
+from psutil.tests import safe_mkdir
+from psutil.tests import safe_rmpath as _safe_rmpath
+from psutil.tests import skip_on_access_denied
+from psutil.tests import TESTFILE_PREFIX
+from psutil.tests import TESTFN
+from psutil.tests import TESTFN_UNICODE
+from psutil.tests import TRAVIS
+from psutil.tests import unittest
+from psutil.tests import unix_socket_path
+import psutil
+
+
+def safe_rmpath(path):
+ if APPVEYOR:
+ # TODO - this is quite random and I'm not sure why it happens,
+ # nor I can reproduce it locally:
+ # https://ci.appveyor.com/project/giampaolo/psutil/build/job/
+ # jiq2cgd6stsbtn60
+ # safe_rmpath() happens after reap_children() so this is weird
+ # Perhaps wait_procs() on Windows is broken? Maybe because
+ # of STILL_ACTIVE?
+ # https://github.com/giampaolo/psutil/blob/
+ # 68c7a70728a31d8b8b58f4be6c4c0baa2f449eda/psutil/arch/
+ # windows/process_info.c#L146
+ try:
+ return _safe_rmpath(path)
+ except WindowsError:
+ traceback.print_exc()
+ else:
+ return _safe_rmpath(path)
+
+
+def subprocess_supports_unicode(name):
+ """Return True if both the fs and the subprocess module can
+ deal with a unicode file name.
+ """
+ if PY3:
+ return True
+ try:
+ safe_rmpath(name)
+ create_exe(name)
+ get_test_subprocess(cmd=[name])
+ except UnicodeEncodeError:
+ return False
+ else:
+ return True
+ finally:
+ reap_children()
+
+
+# An invalid unicode string.
+if PY3:
+ INVALID_NAME = (TESTFN.encode('utf8') + b"f\xc0\x80").decode(
+ 'utf8', 'surrogateescape')
+else:
+ INVALID_NAME = TESTFN + "f\xc0\x80"
+
+
+# ===================================================================
+# FS APIs
+# ===================================================================
+
+
+class _BaseFSAPIsTests(object):
+ funky_name = None
+
+ @classmethod
+ def setUpClass(cls):
+ safe_rmpath(cls.funky_name)
+ create_exe(cls.funky_name)
+
+ @classmethod
+ def tearDownClass(cls):
+ reap_children()
+ safe_rmpath(cls.funky_name)
+
+ def tearDown(self):
+ reap_children()
+
+ def expect_exact_path_match(self):
+ raise NotImplementedError("must be implemented in subclass")
+
+ def test_proc_exe(self):
+ subp = get_test_subprocess(cmd=[self.funky_name])
+ p = psutil.Process(subp.pid)
+ exe = p.exe()
+ self.assertIsInstance(exe, str)
+ if self.expect_exact_path_match():
+ self.assertEqual(exe, self.funky_name)
+
+ def test_proc_name(self):
+ subp = get_test_subprocess(cmd=[self.funky_name])
+ if WINDOWS:
+ # On Windows name() is determined from exe() first, because
+ # it's faster; we want to overcome the internal optimization
+ # and test name() instead of exe().
+ with mock.patch("psutil._psplatform.cext.proc_exe",
+ side_effect=psutil.AccessDenied(os.getpid())) as m:
+ name = psutil.Process(subp.pid).name()
+ assert m.called
+ else:
+ name = psutil.Process(subp.pid).name()
+ self.assertIsInstance(name, str)
+ if self.expect_exact_path_match():
+ self.assertEqual(name, os.path.basename(self.funky_name))
+
+ def test_proc_cmdline(self):
+ subp = get_test_subprocess(cmd=[self.funky_name])
+ p = psutil.Process(subp.pid)
+ cmdline = p.cmdline()
+ for part in cmdline:
+ self.assertIsInstance(part, str)
+ if self.expect_exact_path_match():
+ self.assertEqual(cmdline, [self.funky_name])
+
+ def test_proc_cwd(self):
+ dname = self.funky_name + "2"
+ self.addCleanup(safe_rmpath, dname)
+ safe_mkdir(dname)
+ with chdir(dname):
+ p = psutil.Process()
+ cwd = p.cwd()
+ self.assertIsInstance(p.cwd(), str)
+ if self.expect_exact_path_match():
+ self.assertEqual(cwd, dname)
+
+ def test_proc_open_files(self):
+ p = psutil.Process()
+ start = set(p.open_files())
+ with open(self.funky_name, 'rb'):
+ new = set(p.open_files())
+ path = (new - start).pop().path
+ self.assertIsInstance(path, str)
+ if BSD and not path:
+ # XXX - see https://github.com/giampaolo/psutil/issues/595
+ return self.skipTest("open_files on BSD is broken")
+ if self.expect_exact_path_match():
+ self.assertEqual(os.path.normcase(path),
+ os.path.normcase(self.funky_name))
+
+ @unittest.skipIf(not POSIX, "POSIX only")
+ def test_proc_connections(self):
+ suffix = os.path.basename(self.funky_name)
+ with unix_socket_path(suffix=suffix) as name:
+ try:
+ sock = bind_unix_socket(name)
+ except UnicodeEncodeError:
+ if PY3:
+ raise
+ else:
+ raise unittest.SkipTest("not supported")
+ with closing(sock):
+ conn = psutil.Process().connections('unix')[0]
+ self.assertIsInstance(conn.laddr, str)
+ # AF_UNIX addr not set on OpenBSD
+ if not OPENBSD:
+ self.assertEqual(conn.laddr, name)
+
+ @unittest.skipIf(not POSIX, "POSIX only")
+ @unittest.skipIf(not HAS_CONNECTIONS_UNIX, "can't list UNIX sockets")
+ @skip_on_access_denied()
+ def test_net_connections(self):
+ def find_sock(cons):
+ for conn in cons:
+ if os.path.basename(conn.laddr).startswith(TESTFILE_PREFIX):
+ return conn
+ raise ValueError("connection not found")
+
+ suffix = os.path.basename(self.funky_name)
+ with unix_socket_path(suffix=suffix) as name:
+ try:
+ sock = bind_unix_socket(name)
+ except UnicodeEncodeError:
+ if PY3:
+ raise
+ else:
+ raise unittest.SkipTest("not supported")
+ with closing(sock):
+ cons = psutil.net_connections(kind='unix')
+ # AF_UNIX addr not set on OpenBSD
+ if not OPENBSD:
+ conn = find_sock(cons)
+ self.assertIsInstance(conn.laddr, str)
+ self.assertEqual(conn.laddr, name)
+
+ def test_disk_usage(self):
+ dname = self.funky_name + "2"
+ self.addCleanup(safe_rmpath, dname)
+ safe_mkdir(dname)
+ psutil.disk_usage(dname)
+
+ @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported")
+ @unittest.skipIf(not PY3, "ctypes does not support unicode on PY2")
+ def test_memory_maps(self):
+ # XXX: on Python 2, using ctypes.CDLL with a unicode path
+ # opens a message box which blocks the test run.
+ with copyload_shared_lib(dst_prefix=self.funky_name) as funky_path:
+ def normpath(p):
+ return os.path.realpath(os.path.normcase(p))
+ libpaths = [normpath(x.path)
+ for x in psutil.Process().memory_maps()]
+ # ...just to have a clearer msg in case of failure
+ libpaths = [x for x in libpaths if TESTFILE_PREFIX in x]
+ self.assertIn(normpath(funky_path), libpaths)
+ for path in libpaths:
+ self.assertIsInstance(path, str)
+
+
+@unittest.skipIf(OSX and TRAVIS, "unreliable on TRAVIS") # TODO
+@unittest.skipIf(ASCII_FS, "ASCII fs")
+@unittest.skipIf(not subprocess_supports_unicode(TESTFN_UNICODE),
+ "subprocess can't deal with unicode")
+class TestFSAPIs(_BaseFSAPIsTests, unittest.TestCase):
+ """Test FS APIs with a funky, valid, UTF8 path name."""
+ funky_name = TESTFN_UNICODE
+
+ @classmethod
+ def expect_exact_path_match(cls):
+ # Do not expect psutil to correctly handle unicode paths on
+ # Python 2 if os.listdir() is not able either.
+ if PY3:
+ return True
+ else:
+ here = '.' if isinstance(cls.funky_name, str) else u('.')
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ return cls.funky_name in os.listdir(here)
+
+
+@unittest.skipIf(OSX and TRAVIS, "unreliable on TRAVIS") # TODO
+@unittest.skipIf(not subprocess_supports_unicode(INVALID_NAME),
+ "subprocess can't deal with invalid unicode")
+class TestFSAPIsWithInvalidPath(_BaseFSAPIsTests, unittest.TestCase):
+ """Test FS APIs with a funky, invalid path name."""
+ funky_name = INVALID_NAME
+
+ @classmethod
+ def expect_exact_path_match(cls):
+ # Invalid unicode names are supposed to work on Python 2.
+ return True
+
+
+@unittest.skipIf(not WINDOWS, "WINDOWS only")
+class TestWinProcessName(unittest.TestCase):
+
+ def test_name_type(self):
+ # On Windows name() is determined from exe() first, because
+ # it's faster; we want to overcome the internal optimization
+ # and test name() instead of exe().
+ with mock.patch("psutil._psplatform.cext.proc_exe",
+ side_effect=psutil.AccessDenied(os.getpid())) as m:
+ self.assertIsInstance(psutil.Process().name(), str)
+ assert m.called
+
+
+# ===================================================================
+# Non fs APIs
+# ===================================================================
+
+
+class TestNonFSAPIS(unittest.TestCase):
+ """Unicode tests for non fs-related APIs."""
+
+ def tearDown(self):
+ reap_children()
+
+ @unittest.skipIf(not HAS_ENVIRON, "not supported")
+ def test_proc_environ(self):
+ # Note: differently from others, this test does not deal
+ # with fs paths. On Python 2 subprocess module is broken as
+ # it's not able to handle with non-ASCII env vars, so
+ # we use "è", which is part of the extended ASCII table
+ # (unicode point <= 255).
+ env = os.environ.copy()
+ funky_str = TESTFN_UNICODE if PY3 else 'è'
+ env['FUNNY_ARG'] = funky_str
+ sproc = get_test_subprocess(env=env)
+ p = psutil.Process(sproc.pid)
+ env = p.environ()
+ for k, v in env.items():
+ self.assertIsInstance(k, str)
+ self.assertIsInstance(v, str)
+ self.assertEqual(env['FUNNY_ARG'], funky_str)
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)
diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py
new file mode 100755
index 00000000..32c46f67
--- /dev/null
+++ b/psutil/tests/test_windows.py
@@ -0,0 +1,837 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Windows specific tests."""
+
+import datetime
+import errno
+import glob
+import os
+import platform
+import re
+import signal
+import subprocess
+import sys
+import time
+import warnings
+
+import psutil
+from psutil import WINDOWS
+from psutil.tests import APPVEYOR
+from psutil.tests import get_test_subprocess
+from psutil.tests import HAS_BATTERY
+from psutil.tests import mock
+from psutil.tests import reap_children
+from psutil.tests import retry_before_failing
+from psutil.tests import run_test_module_by_name
+from psutil.tests import sh
+from psutil.tests import unittest
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ try:
+ import win32api # requires "pip install pypiwin32"
+ import win32con
+ import win32process
+ import wmi # requires "pip install wmi" / "make setup-dev-env"
+ except ImportError:
+ if os.name == 'nt':
+ raise
+
+
+cext = psutil._psplatform.cext
+
+# are we a 64 bit process
+IS_64_BIT = sys.maxsize > 2**32
+
+
+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:
+ raise psutil.NoSuchProcess(None, None)
+ raise
+ return wrapper
+
+
+# ===================================================================
+# System APIs
+# ===================================================================
+
+
+@unittest.skipIf(not WINDOWS, "WINDOWS only")
+class TestSystemAPIs(unittest.TestCase):
+
+ def test_nic_names(self):
+ out = sh('ipconfig /all')
+ nics = psutil.net_io_counters(pernic=True).keys()
+ for nic in nics:
+ if "pseudo-interface" in nic.replace(' ', '-').lower():
+ continue
+ if nic not in out:
+ self.fail(
+ "%r nic wasn't found in 'ipconfig /all' output" % nic)
+
+ @unittest.skipIf('NUMBER_OF_PROCESSORS' not in os.environ,
+ 'NUMBER_OF_PROCESSORS env var is not available')
+ def test_cpu_count(self):
+ num_cpus = int(os.environ['NUMBER_OF_PROCESSORS'])
+ self.assertEqual(num_cpus, psutil.cpu_count())
+
+ def test_cpu_count_2(self):
+ sys_value = win32api.GetSystemInfo()[5]
+ psutil_value = psutil.cpu_count()
+ self.assertEqual(sys_value, psutil_value)
+
+ def test_cpu_freq(self):
+ w = wmi.WMI()
+ proc = w.Win32_Processor()[0]
+ self.assertEqual(proc.CurrentClockSpeed, psutil.cpu_freq().current)
+ self.assertEqual(proc.MaxClockSpeed, psutil.cpu_freq().max)
+
+ def test_total_phymem(self):
+ w = wmi.WMI().Win32_ComputerSystem()[0]
+ self.assertEqual(int(w.TotalPhysicalMemory),
+ psutil.virtual_memory().total)
+
+ # @unittest.skipIf(wmi is None, "wmi module is not installed")
+ # def test__UPTIME(self):
+ # # _UPTIME constant is not public but it is used internally
+ # # as value to return for pid 0 creation time.
+ # # WMI behaves the same.
+ # w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
+ # p = psutil.Process(0)
+ # wmic_create = str(w.CreationDate.split('.')[0])
+ # psutil_create = time.strftime("%Y%m%d%H%M%S",
+ # time.localtime(p.create_time()))
+
+ # Note: this test is not very reliable
+ @unittest.skipIf(APPVEYOR, "test not relieable on appveyor")
+ @retry_before_failing()
+ def test_pids(self):
+ # Note: this test might fail if the OS is starting/killing
+ # other processes in the meantime
+ w = wmi.WMI().Win32_Process()
+ wmi_pids = set([x.ProcessId for x in w])
+ psutil_pids = set(psutil.pids())
+ self.assertEqual(wmi_pids, psutil_pids)
+
+ @retry_before_failing()
+ def test_disks(self):
+ ps_parts = psutil.disk_partitions(all=True)
+ wmi_parts = wmi.WMI().Win32_LogicalDisk()
+ for ps_part in ps_parts:
+ for wmi_part in wmi_parts:
+ if ps_part.device.replace('\\', '') == wmi_part.DeviceID:
+ if not ps_part.mountpoint:
+ # this is usually a CD-ROM with no disk inserted
+ break
+ try:
+ usage = psutil.disk_usage(ps_part.mountpoint)
+ except OSError as err:
+ if err.errno == errno.ENOENT:
+ # usually this is the floppy
+ break
+ else:
+ raise
+ self.assertEqual(usage.total, int(wmi_part.Size))
+ wmi_free = int(wmi_part.FreeSpace)
+ self.assertEqual(usage.free, wmi_free)
+ # 10 MB tollerance
+ if abs(usage.free - wmi_free) > 10 * 1024 * 1024:
+ self.fail("psutil=%s, wmi=%s" % (
+ usage.free, wmi_free))
+ break
+ else:
+ self.fail("can't find partition %s" % repr(ps_part))
+
+ def test_disk_usage(self):
+ for disk in psutil.disk_partitions():
+ sys_value = win32api.GetDiskFreeSpaceEx(disk.mountpoint)
+ psutil_value = psutil.disk_usage(disk.mountpoint)
+ self.assertAlmostEqual(sys_value[0], psutil_value.free,
+ delta=1024 * 1024)
+ self.assertAlmostEqual(sys_value[1], psutil_value.total,
+ delta=1024 * 1024)
+ self.assertEqual(psutil_value.used,
+ psutil_value.total - psutil_value.free)
+
+ def test_disk_partitions(self):
+ sys_value = [
+ x + '\\' for x in win32api.GetLogicalDriveStrings().split("\\\x00")
+ if x and not x.startswith('A:')]
+ psutil_value = [x.mountpoint for x in psutil.disk_partitions(all=True)]
+ self.assertEqual(sys_value, psutil_value)
+
+ def test_net_if_stats(self):
+ ps_names = set(cext.net_if_stats())
+ wmi_adapters = wmi.WMI().Win32_NetworkAdapter()
+ wmi_names = set()
+ for wmi_adapter in wmi_adapters:
+ wmi_names.add(wmi_adapter.Name)
+ wmi_names.add(wmi_adapter.NetConnectionID)
+ self.assertTrue(ps_names & wmi_names,
+ "no common entries in %s, %s" % (ps_names, wmi_names))
+
+ def test_boot_time(self):
+ wmi_os = wmi.WMI().Win32_OperatingSystem()
+ wmi_btime_str = wmi_os[0].LastBootUpTime.split('.')[0]
+ wmi_btime_dt = datetime.datetime.strptime(
+ wmi_btime_str, "%Y%m%d%H%M%S")
+ psutil_dt = datetime.datetime.fromtimestamp(psutil.boot_time())
+ diff = abs((wmi_btime_dt - psutil_dt).total_seconds())
+ # Wmic time is 2-3 secs lower for some reason; that's OK.
+ self.assertLessEqual(diff, 3)
+
+ def test_boot_time_fluctuation(self):
+ # https://github.com/giampaolo/psutil/issues/1007
+ with mock.patch('psutil._pswindows.cext.boot_time', return_value=5):
+ self.assertEqual(psutil.boot_time(), 5)
+ with mock.patch('psutil._pswindows.cext.boot_time', return_value=4):
+ self.assertEqual(psutil.boot_time(), 5)
+ with mock.patch('psutil._pswindows.cext.boot_time', return_value=6):
+ self.assertEqual(psutil.boot_time(), 5)
+ with mock.patch('psutil._pswindows.cext.boot_time', return_value=333):
+ self.assertEqual(psutil.boot_time(), 333)
+
+
+# ===================================================================
+# sensors_battery()
+# ===================================================================
+
+
+@unittest.skipIf(not WINDOWS, "WINDOWS only")
+class TestSensorsBattery(unittest.TestCase):
+
+ def test_has_battery(self):
+ if win32api.GetPwrCapabilities()['SystemBatteriesPresent']:
+ self.assertIsNotNone(psutil.sensors_battery())
+ else:
+ self.assertIsNone(psutil.sensors_battery())
+
+ @unittest.skipIf(not HAS_BATTERY, "no battery")
+ def test_percent(self):
+ w = wmi.WMI()
+ battery_wmi = w.query('select * from Win32_Battery')[0]
+ battery_psutil = psutil.sensors_battery()
+ self.assertAlmostEqual(
+ battery_psutil.percent, battery_wmi.EstimatedChargeRemaining,
+ delta=1)
+
+ @unittest.skipIf(not HAS_BATTERY, "no battery")
+ def test_power_plugged(self):
+ w = wmi.WMI()
+ battery_wmi = w.query('select * from Win32_Battery')[0]
+ battery_psutil = psutil.sensors_battery()
+ # Status codes:
+ # https://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx
+ self.assertEqual(battery_psutil.power_plugged,
+ battery_wmi.BatteryStatus == 2)
+
+ def test_emulate_no_battery(self):
+ with mock.patch("psutil._pswindows.cext.sensors_battery",
+ return_value=(0, 128, 0, 0)) as m:
+ self.assertIsNone(psutil.sensors_battery())
+ assert m.called
+
+ def test_emulate_power_connected(self):
+ with mock.patch("psutil._pswindows.cext.sensors_battery",
+ return_value=(1, 0, 0, 0)) as m:
+ self.assertEqual(psutil.sensors_battery().secsleft,
+ psutil.POWER_TIME_UNLIMITED)
+ assert m.called
+
+ def test_emulate_power_charging(self):
+ with mock.patch("psutil._pswindows.cext.sensors_battery",
+ return_value=(0, 8, 0, 0)) as m:
+ self.assertEqual(psutil.sensors_battery().secsleft,
+ psutil.POWER_TIME_UNLIMITED)
+ assert m.called
+
+ def test_emulate_secs_left_unknown(self):
+ with mock.patch("psutil._pswindows.cext.sensors_battery",
+ return_value=(0, 0, 0, -1)) as m:
+ self.assertEqual(psutil.sensors_battery().secsleft,
+ psutil.POWER_TIME_UNKNOWN)
+ assert m.called
+
+
+# ===================================================================
+# Process APIs
+# ===================================================================
+
+
+@unittest.skipIf(not WINDOWS, "WINDOWS only")
+class TestProcess(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.pid = get_test_subprocess().pid
+
+ @classmethod
+ def tearDownClass(cls):
+ reap_children()
+
+ def test_issue_24(self):
+ p = psutil.Process(0)
+ self.assertRaises(psutil.AccessDenied, p.kill)
+
+ def test_special_pid(self):
+ p = psutil.Process(4)
+ self.assertEqual(p.name(), 'System')
+ # use __str__ to access all common Process properties to check
+ # that nothing strange happens
+ str(p)
+ p.username()
+ self.assertTrue(p.create_time() >= 0.0)
+ try:
+ rss, vms = p.memory_info()[:2]
+ except psutil.AccessDenied:
+ # expected on Windows Vista and Windows 7
+ if not platform.uname()[1] in ('vista', 'win-7', 'win7'):
+ raise
+ else:
+ self.assertTrue(rss > 0)
+
+ def test_send_signal(self):
+ p = psutil.Process(self.pid)
+ self.assertRaises(ValueError, p.send_signal, signal.SIGINT)
+
+ def test_exe(self):
+ for p in psutil.process_iter():
+ try:
+ self.assertEqual(os.path.basename(p.exe()), p.name())
+ except psutil.Error:
+ pass
+
+ def test_num_handles_increment(self):
+ p = psutil.Process(os.getpid())
+ before = p.num_handles()
+ handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
+ win32con.FALSE, os.getpid())
+ after = p.num_handles()
+ self.assertEqual(after, before + 1)
+ win32api.CloseHandle(handle)
+ self.assertEqual(p.num_handles(), before)
+
+ def test_handles_leak(self):
+ # Call all Process methods and make sure no handles are left
+ # open. This is here mainly to make sure functions using
+ # OpenProcess() always call CloseHandle().
+ def call(p, attr):
+ attr = getattr(p, name, None)
+ if attr is not None and callable(attr):
+ attr()
+ else:
+ attr
+
+ p = psutil.Process(self.pid)
+ failures = []
+ for name in dir(psutil.Process):
+ if name.startswith('_') \
+ or name in ('terminate', 'kill', 'suspend', 'resume',
+ 'nice', 'send_signal', 'wait', 'children',
+ 'as_dict', 'memory_info_ex'):
+ continue
+ else:
+ try:
+ call(p, name)
+ num1 = p.num_handles()
+ call(p, name)
+ num2 = p.num_handles()
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ pass
+ else:
+ if num2 > num1:
+ fail = \
+ "failure while processing Process.%s method " \
+ "(before=%s, after=%s)" % (name, num1, num2)
+ failures.append(fail)
+ if failures:
+ self.fail('\n' + '\n'.join(failures))
+
+ def test_name_always_available(self):
+ # On Windows name() is never supposed to raise AccessDenied,
+ # see https://github.com/giampaolo/psutil/issues/627
+ for p in psutil.process_iter():
+ try:
+ p.name()
+ except psutil.NoSuchProcess:
+ pass
+
+ @unittest.skipIf(not sys.version_info >= (2, 7),
+ "CTRL_* signals not supported")
+ def test_ctrl_signals(self):
+ p = psutil.Process(get_test_subprocess().pid)
+ p.send_signal(signal.CTRL_C_EVENT)
+ p.send_signal(signal.CTRL_BREAK_EVENT)
+ p.kill()
+ p.wait()
+ self.assertRaises(psutil.NoSuchProcess,
+ p.send_signal, signal.CTRL_C_EVENT)
+ self.assertRaises(psutil.NoSuchProcess,
+ p.send_signal, signal.CTRL_BREAK_EVENT)
+
+ def test_compare_name_exe(self):
+ for p in psutil.process_iter():
+ try:
+ a = os.path.basename(p.exe())
+ b = p.name()
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ pass
+ else:
+ self.assertEqual(a, b)
+
+ def test_username(self):
+ self.assertEqual(psutil.Process().username(),
+ win32api.GetUserNameEx(win32con.NameSamCompatible))
+
+ def test_cmdline(self):
+ sys_value = re.sub(' +', ' ', win32api.GetCommandLine()).strip()
+ psutil_value = ' '.join(psutil.Process().cmdline())
+ self.assertEqual(sys_value, psutil_value)
+
+ # XXX - occasional failures
+
+ # def test_cpu_times(self):
+ # handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
+ # win32con.FALSE, os.getpid())
+ # self.addCleanup(win32api.CloseHandle, handle)
+ # sys_value = win32process.GetProcessTimes(handle)
+ # psutil_value = psutil.Process().cpu_times()
+ # self.assertAlmostEqual(
+ # psutil_value.user, sys_value['UserTime'] / 10000000.0,
+ # delta=0.2)
+ # self.assertAlmostEqual(
+ # psutil_value.user, sys_value['KernelTime'] / 10000000.0,
+ # delta=0.2)
+
+ def test_nice(self):
+ handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
+ win32con.FALSE, os.getpid())
+ self.addCleanup(win32api.CloseHandle, handle)
+ sys_value = win32process.GetPriorityClass(handle)
+ psutil_value = psutil.Process().nice()
+ self.assertEqual(psutil_value, sys_value)
+
+ def test_memory_info(self):
+ handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
+ win32con.FALSE, self.pid)
+ self.addCleanup(win32api.CloseHandle, handle)
+ sys_value = win32process.GetProcessMemoryInfo(handle)
+ psutil_value = psutil.Process(self.pid).memory_info()
+ self.assertEqual(
+ sys_value['PeakWorkingSetSize'], psutil_value.peak_wset)
+ self.assertEqual(
+ sys_value['WorkingSetSize'], psutil_value.wset)
+ self.assertEqual(
+ sys_value['QuotaPeakPagedPoolUsage'], psutil_value.peak_paged_pool)
+ self.assertEqual(
+ sys_value['QuotaPagedPoolUsage'], psutil_value.paged_pool)
+ self.assertEqual(
+ sys_value['QuotaPeakNonPagedPoolUsage'],
+ psutil_value.peak_nonpaged_pool)
+ self.assertEqual(
+ sys_value['QuotaNonPagedPoolUsage'], psutil_value.nonpaged_pool)
+ self.assertEqual(
+ sys_value['PagefileUsage'], psutil_value.pagefile)
+ self.assertEqual(
+ sys_value['PeakPagefileUsage'], psutil_value.peak_pagefile)
+
+ self.assertEqual(psutil_value.rss, psutil_value.wset)
+ self.assertEqual(psutil_value.vms, psutil_value.pagefile)
+
+ def test_wait(self):
+ handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
+ win32con.FALSE, self.pid)
+ self.addCleanup(win32api.CloseHandle, handle)
+ p = psutil.Process(self.pid)
+ p.terminate()
+ psutil_value = p.wait()
+ sys_value = win32process.GetExitCodeProcess(handle)
+ self.assertEqual(psutil_value, sys_value)
+
+ def test_cpu_affinity(self):
+ def from_bitmask(x):
+ return [i for i in range(64) if (1 << i) & x]
+
+ handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
+ win32con.FALSE, self.pid)
+ self.addCleanup(win32api.CloseHandle, handle)
+ sys_value = from_bitmask(
+ win32process.GetProcessAffinityMask(handle)[0])
+ psutil_value = psutil.Process(self.pid).cpu_affinity()
+ self.assertEqual(psutil_value, sys_value)
+
+ def test_io_counters(self):
+ handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
+ win32con.FALSE, os.getpid())
+ self.addCleanup(win32api.CloseHandle, handle)
+ sys_value = win32process.GetProcessIoCounters(handle)
+ psutil_value = psutil.Process().io_counters()
+ self.assertEqual(
+ psutil_value.read_count, sys_value['ReadOperationCount'])
+ self.assertEqual(
+ psutil_value.write_count, sys_value['WriteOperationCount'])
+ self.assertEqual(
+ psutil_value.read_bytes, sys_value['ReadTransferCount'])
+ self.assertEqual(
+ psutil_value.write_bytes, sys_value['WriteTransferCount'])
+ self.assertEqual(
+ psutil_value.other_count, sys_value['OtherOperationCount'])
+ self.assertEqual(
+ psutil_value.other_bytes, sys_value['OtherTransferCount'])
+
+ def test_num_handles(self):
+ import ctypes
+ import ctypes.wintypes
+ PROCESS_QUERY_INFORMATION = 0x400
+ handle = ctypes.windll.kernel32.OpenProcess(
+ PROCESS_QUERY_INFORMATION, 0, os.getpid())
+ self.addCleanup(ctypes.windll.kernel32.CloseHandle, handle)
+ hndcnt = ctypes.wintypes.DWORD()
+ ctypes.windll.kernel32.GetProcessHandleCount(
+ handle, ctypes.byref(hndcnt))
+ sys_value = hndcnt.value
+ psutil_value = psutil.Process().num_handles()
+ ctypes.windll.kernel32.CloseHandle(handle)
+ self.assertEqual(psutil_value, sys_value + 1)
+
+
+@unittest.skipIf(not WINDOWS, "WINDOWS only")
+class TestProcessWMI(unittest.TestCase):
+ """Compare Process API results with WMI."""
+
+ @classmethod
+ def setUpClass(cls):
+ cls.pid = get_test_subprocess().pid
+
+ @classmethod
+ def tearDownClass(cls):
+ reap_children()
+
+ def test_name(self):
+ w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
+ p = psutil.Process(self.pid)
+ self.assertEqual(p.name(), w.Caption)
+
+ def test_exe(self):
+ w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
+ p = psutil.Process(self.pid)
+ # Note: wmi reports the exe as a lower case string.
+ # Being Windows paths case-insensitive we ignore that.
+ self.assertEqual(p.exe().lower(), w.ExecutablePath.lower())
+
+ def test_cmdline(self):
+ w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
+ p = psutil.Process(self.pid)
+ self.assertEqual(' '.join(p.cmdline()),
+ w.CommandLine.replace('"', ''))
+
+ def test_username(self):
+ w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
+ p = psutil.Process(self.pid)
+ domain, _, username = w.GetOwner()
+ username = "%s\\%s" % (domain, username)
+ self.assertEqual(p.username(), username)
+
+ def test_memory_rss(self):
+ time.sleep(0.1)
+ w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
+ p = psutil.Process(self.pid)
+ rss = p.memory_info().rss
+ self.assertEqual(rss, int(w.WorkingSetSize))
+
+ def test_memory_vms(self):
+ time.sleep(0.1)
+ w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
+ p = psutil.Process(self.pid)
+ vms = p.memory_info().vms
+ # http://msdn.microsoft.com/en-us/library/aa394372(VS.85).aspx
+ # ...claims that PageFileUsage is represented in Kilo
+ # bytes but funnily enough on certain platforms bytes are
+ # returned instead.
+ wmi_usage = int(w.PageFileUsage)
+ if (vms != wmi_usage) and (vms != wmi_usage * 1024):
+ self.fail("wmi=%s, psutil=%s" % (wmi_usage, vms))
+
+ def test_create_time(self):
+ w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
+ p = psutil.Process(self.pid)
+ wmic_create = str(w.CreationDate.split('.')[0])
+ psutil_create = time.strftime("%Y%m%d%H%M%S",
+ time.localtime(p.create_time()))
+ self.assertEqual(wmic_create, psutil_create)
+
+
+@unittest.skipIf(not WINDOWS, "WINDOWS only")
+class TestDualProcessImplementation(unittest.TestCase):
+ """
+ Certain APIs on Windows have 2 internal implementations, one
+ based on documented Windows APIs, another one based
+ NtQuerySystemInformation() which gets called as fallback in
+ case the first fails because of limited permission error.
+ Here we test that the two methods return the exact same value,
+ see:
+ https://github.com/giampaolo/psutil/issues/304
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ cls.pid = get_test_subprocess().pid
+
+ @classmethod
+ def tearDownClass(cls):
+ reap_children()
+ # ---
+ # same tests as above but mimicks the AccessDenied failure of
+ # the first (fast) method failing with AD.
+
+ def test_name(self):
+ name = psutil.Process(self.pid).name()
+ with mock.patch("psutil._psplatform.cext.proc_exe",
+ side_effect=psutil.AccessDenied(os.getpid())) as fun:
+ self.assertEqual(psutil.Process(self.pid).name(), name)
+ assert fun.called
+
+ def test_memory_info(self):
+ mem_1 = psutil.Process(self.pid).memory_info()
+ with mock.patch("psutil._psplatform.cext.proc_memory_info",
+ side_effect=OSError(errno.EPERM, "msg")) as fun:
+ mem_2 = psutil.Process(self.pid).memory_info()
+ self.assertEqual(len(mem_1), len(mem_2))
+ for i in range(len(mem_1)):
+ self.assertGreaterEqual(mem_1[i], 0)
+ self.assertGreaterEqual(mem_2[i], 0)
+ self.assertAlmostEqual(mem_1[i], mem_2[i], delta=512)
+ assert fun.called
+
+ def test_create_time(self):
+ ctime = psutil.Process(self.pid).create_time()
+ with mock.patch("psutil._psplatform.cext.proc_create_time",
+ side_effect=OSError(errno.EPERM, "msg")) as fun:
+ self.assertEqual(psutil.Process(self.pid).create_time(), ctime)
+ assert fun.called
+
+ def test_cpu_times(self):
+ cpu_times_1 = psutil.Process(self.pid).cpu_times()
+ with mock.patch("psutil._psplatform.cext.proc_cpu_times",
+ side_effect=OSError(errno.EPERM, "msg")) as fun:
+ cpu_times_2 = psutil.Process(self.pid).cpu_times()
+ assert fun.called
+ self.assertAlmostEqual(
+ cpu_times_1.user, cpu_times_2.user, delta=0.01)
+ self.assertAlmostEqual(
+ cpu_times_1.system, cpu_times_2.system, delta=0.01)
+
+ def test_io_counters(self):
+ io_counters_1 = psutil.Process(self.pid).io_counters()
+ with mock.patch("psutil._psplatform.cext.proc_io_counters",
+ side_effect=OSError(errno.EPERM, "msg")) as fun:
+ io_counters_2 = psutil.Process(self.pid).io_counters()
+ for i in range(len(io_counters_1)):
+ self.assertAlmostEqual(
+ io_counters_1[i], io_counters_2[i], delta=5)
+ assert fun.called
+
+ def test_num_handles(self):
+ num_handles = psutil.Process(self.pid).num_handles()
+ with mock.patch("psutil._psplatform.cext.proc_num_handles",
+ side_effect=OSError(errno.EPERM, "msg")) as fun:
+ self.assertEqual(psutil.Process(self.pid).num_handles(),
+ num_handles)
+ assert fun.called
+
+
+@unittest.skipIf(not WINDOWS, "WINDOWS only")
+class RemoteProcessTestCase(unittest.TestCase):
+ """Certain functions require calling ReadProcessMemory.
+ This trivially works when called on the current process.
+ Check that this works on other processes, especially when they
+ have a different bitness.
+ """
+
+ @staticmethod
+ def find_other_interpreter():
+ # find a python interpreter that is of the opposite bitness from us
+ code = "import sys; sys.stdout.write(str(sys.maxsize > 2**32))"
+
+ # XXX: a different and probably more stable approach might be to access
+ # the registry but accessing 64 bit paths from a 32 bit process
+ for filename in glob.glob(r"C:\Python*\python.exe"):
+ proc = subprocess.Popen(args=[filename, "-c", code],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ output, _ = proc.communicate()
+ if output == str(not IS_64_BIT):
+ return filename
+
+ @classmethod
+ def setUpClass(cls):
+ other_python = cls.find_other_interpreter()
+
+ if other_python is None:
+ raise unittest.SkipTest(
+ "could not find interpreter with opposite bitness")
+
+ if IS_64_BIT:
+ cls.python64 = sys.executable
+ cls.python32 = other_python
+ else:
+ cls.python64 = other_python
+ cls.python32 = sys.executable
+
+ test_args = ["-c", "import sys; sys.stdin.read()"]
+
+ def setUp(self):
+ env = os.environ.copy()
+ env["THINK_OF_A_NUMBER"] = str(os.getpid())
+ self.proc32 = get_test_subprocess([self.python32] + self.test_args,
+ env=env,
+ stdin=subprocess.PIPE)
+ self.proc64 = get_test_subprocess([self.python64] + self.test_args,
+ env=env,
+ stdin=subprocess.PIPE)
+
+ def tearDown(self):
+ self.proc32.communicate()
+ self.proc64.communicate()
+ reap_children()
+
+ @classmethod
+ def tearDownClass(cls):
+ reap_children()
+
+ def test_cmdline_32(self):
+ p = psutil.Process(self.proc32.pid)
+ self.assertEqual(len(p.cmdline()), 3)
+ self.assertEqual(p.cmdline()[1:], self.test_args)
+
+ def test_cmdline_64(self):
+ p = psutil.Process(self.proc64.pid)
+ self.assertEqual(len(p.cmdline()), 3)
+ self.assertEqual(p.cmdline()[1:], self.test_args)
+
+ def test_cwd_32(self):
+ p = psutil.Process(self.proc32.pid)
+ self.assertEqual(p.cwd(), os.getcwd())
+
+ def test_cwd_64(self):
+ p = psutil.Process(self.proc64.pid)
+ self.assertEqual(p.cwd(), os.getcwd())
+
+ def test_environ_32(self):
+ p = psutil.Process(self.proc32.pid)
+ e = p.environ()
+ self.assertIn("THINK_OF_A_NUMBER", e)
+ self.assertEquals(e["THINK_OF_A_NUMBER"], str(os.getpid()))
+
+ def test_environ_64(self):
+ p = psutil.Process(self.proc64.pid)
+ e = p.environ()
+ self.assertIn("THINK_OF_A_NUMBER", e)
+ self.assertEquals(e["THINK_OF_A_NUMBER"], str(os.getpid()))
+
+
+# ===================================================================
+# Windows services
+# ===================================================================
+
+
+@unittest.skipIf(not WINDOWS, "WINDOWS only")
+class TestServices(unittest.TestCase):
+
+ def test_win_service_iter(self):
+ valid_statuses = set([
+ "running",
+ "paused",
+ "start",
+ "pause",
+ "continue",
+ "stop",
+ "stopped",
+ ])
+ valid_start_types = set([
+ "automatic",
+ "manual",
+ "disabled",
+ ])
+ valid_statuses = set([
+ "running",
+ "paused",
+ "start_pending",
+ "pause_pending",
+ "continue_pending",
+ "stop_pending",
+ "stopped"
+ ])
+ for serv in psutil.win_service_iter():
+ data = serv.as_dict()
+ self.assertIsInstance(data['name'], str)
+ self.assertNotEqual(data['name'].strip(), "")
+ self.assertIsInstance(data['display_name'], str)
+ self.assertIsInstance(data['username'], str)
+ self.assertIn(data['status'], valid_statuses)
+ if data['pid'] is not None:
+ psutil.Process(data['pid'])
+ self.assertIsInstance(data['binpath'], str)
+ self.assertIsInstance(data['username'], str)
+ self.assertIsInstance(data['start_type'], str)
+ self.assertIn(data['start_type'], valid_start_types)
+ self.assertIn(data['status'], valid_statuses)
+ self.assertIsInstance(data['description'], str)
+ pid = serv.pid()
+ if pid is not None:
+ p = psutil.Process(pid)
+ self.assertTrue(p.is_running())
+ # win_service_get
+ s = psutil.win_service_get(serv.name())
+ # test __eq__
+ self.assertEqual(serv, s)
+
+ def test_win_service_get(self):
+ name = next(psutil.win_service_iter()).name()
+
+ with self.assertRaises(psutil.NoSuchProcess) as cm:
+ psutil.win_service_get(name + '???')
+ self.assertEqual(cm.exception.name, name + '???')
+
+ # test NoSuchProcess
+ service = psutil.win_service_get(name)
+ exc = WindowsError(
+ psutil._psplatform.cext.ERROR_SERVICE_DOES_NOT_EXIST, "")
+ with mock.patch("psutil._psplatform.cext.winservice_query_status",
+ side_effect=exc):
+ self.assertRaises(psutil.NoSuchProcess, service.status)
+ with mock.patch("psutil._psplatform.cext.winservice_query_config",
+ side_effect=exc):
+ self.assertRaises(psutil.NoSuchProcess, service.username)
+
+ # test AccessDenied
+ exc = WindowsError(
+ psutil._psplatform.cext.ERROR_ACCESS_DENIED, "")
+ with mock.patch("psutil._psplatform.cext.winservice_query_status",
+ side_effect=exc):
+ self.assertRaises(psutil.AccessDenied, service.status)
+ with mock.patch("psutil._psplatform.cext.winservice_query_config",
+ side_effect=exc):
+ self.assertRaises(psutil.AccessDenied, service.username)
+
+ # test __str__ and __repr__
+ self.assertIn(service.name(), str(service))
+ self.assertIn(service.display_name(), str(service))
+ self.assertIn(service.name(), repr(service))
+ self.assertIn(service.display_name(), repr(service))
+
+
+if __name__ == '__main__':
+ run_test_module_by_name(__file__)