diff --git a/psutil/__init__.py b/psutil/__init__.py index 157b82aebf..2abdf68c2d 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -16,6 +16,7 @@ - NetBSD - Sun Solaris - AIX + - Cygwin (experimental) Works with Python versions from 2.6 to 3.4+. """ @@ -77,6 +78,7 @@ from ._common import AIX from ._common import BSD +from ._common import CYGWIN from ._common import FREEBSD # NOQA from ._common import LINUX from ._common import MACOS @@ -173,6 +175,10 @@ # via sys.modules. PROCFS_PATH = "/proc" +elif CYGWIN: + PROCFS_PATH = "/proc" + from . import _pscygwin as _psplatform + else: # pragma: no cover raise NotImplementedError('platform %s is not supported' % sys.platform) @@ -201,7 +207,7 @@ "POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED", "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "MACOS", "OSX", "POSIX", - "SUNOS", "WINDOWS", "AIX", + "SUNOS", "WINDOWS", "AIX", "CYGWIN", # classes "Process", "Popen", @@ -357,7 +363,7 @@ def __init__(self, seconds, pid=None, name=None): if POSIX: from . import _psposix _psposix.TimeoutExpired = TimeoutExpired -if LINUX: +if LINUX or CYGWIN: from . import _pslinux_common _pslinux_common.NoSuchProcess = NoSuchProcess _pslinux_common.ZombieProcess = ZombieProcess @@ -662,11 +668,16 @@ def parent(self): if self.pid == lowest_pid: return None ppid = self.ppid() + if CYGWIN: + # See note for Process.create_time in _pscygwin.py + rounding_error = 1 + else: + rounding_error = 0 if ppid is not None: ctime = self.create_time() try: parent = Process(ppid) - if parent.create_time() <= ctime: + if parent.create_time() <= (ctime + rounding_error): return parent # ...else ppid has been reused by another process except NoSuchProcess: @@ -1495,7 +1506,15 @@ def pids(): """Return a list of current running PIDs.""" global _LOWEST_PID ret = sorted(_psplatform.pids()) - _LOWEST_PID = ret[0] + + if CYGWIN: + # Note: The _LOWEST_PID concept is not very meaningful since every + # "top-level" process has a non-1 PID; they do have PPID of 1 but that + # does not correspend to an actual process that shows up in the pids() + # list + _LOWEST_PID = 1 + else: + _LOWEST_PID = ret[0] return ret @@ -2467,7 +2486,7 @@ def test(): # pragma: no cover else: cputime = '' - user = p.info['username'] + user = p.info['username'] or '' if not user and POSIX: try: user = p.uids()[0] diff --git a/psutil/_common.py b/psutil/_common.py index 40fdbe25e0..38ce6f7539 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -44,7 +44,7 @@ __all__ = [ # constants 'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX', - 'SUNOS', 'WINDOWS', + 'SUNOS', 'WINDOWS', 'CYGWIN', 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', # connection constants 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED', @@ -86,6 +86,7 @@ BSD = FREEBSD or OPENBSD or NETBSD SUNOS = sys.platform.startswith(("sunos", "solaris")) AIX = sys.platform.startswith("aix") +CYGWIN = sys.platform.startswith("cygwin") # =================================================================== diff --git a/psutil/_pscygwin.py b/psutil/_pscygwin.py new file mode 100644 index 0000000000..04ed70ab9f --- /dev/null +++ b/psutil/_pscygwin.py @@ -0,0 +1,397 @@ +"""Cygwin platform implementation.""" + +from __future__ import division + +import errno +import os +import sys +from collections import namedtuple + +from . import _pslinux_common +from . import _psposix +from . import _psutil_cygwin as cext # NOQA +from ._common import get_procfs_path +from ._common import memoize_when_activated +from ._common import open_binary +from ._common import usage_percent +from ._pslinux_common import Process as BaseProcess +from ._pslinux_common import CLOCK_TICKS +from ._pslinux_common import wrap_exceptions + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + + +__extra__all__ = ["PROCFS_PATH"] + + +# ===================================================================== +# --- globals +# ===================================================================== + +if enum is None: + AF_LINK = -1 +else: + AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1}) + AF_LINK = AddressFamily.AF_LINK + + +# These objects get set on "import psutil" from the __init__.py +# file, see: https://github.com/giampaolo/psutil/issues/1402 +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# ===================================================================== +# --- named tuples +# ===================================================================== + +# psutil.virtual_memory() +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) + +scputimes = namedtuple('scputimes', ['user', 'system', 'idle']) + +pmem = pfullmem = _pslinux_common.pmem + + +# ===================================================================== +# --- other system functions +# ===================================================================== + + +boot_time = _pslinux_common.boot_time + + +# ===================================================================== +# --- system memory +# ===================================================================== + + +def virtual_memory(): + """Report virtual memory stats. + + This is implemented similarly as on Linux by reading /proc/meminfo; + however the level of detail available in Cygwin's /proc/meminfo is + significantly less than on Linux and the memory manager is still + that of the NT kernel so it does not make sense to try to use memory + calculations for Linux. + + The resulting virtual memory stats are the same as those that would + be obtained with the Windows support module. + """ + mems = {} + with open_binary('%s/meminfo' % get_procfs_path()) as f: + for line in f: + fields = [n.rstrip(b': ') for n in line.split()] + mems[fields[0]] = int(fields[1]) * 1024 + + total = mems[b'MemTotal'] + avail = free = mems[b'MemFree'] + used = total - free + percent = usage_percent(used, total, round_=1) + return svmem(total, avail, percent, used, free) + + +def swap_memory(): + """Return swap memory metrics.""" + raise NotImplementedError("swap_memory not implemented on Cygwin") + + +# ===================================================================== +# --- CPUs +# ===================================================================== + + +def cpu_times(): + """Return a named tuple representing the following system-wide + CPU times: + (user, system, idle) + """ + procfs_path = get_procfs_path() + with open_binary('%s/stat' % procfs_path) as f: + values = f.readline().split() + fields = values[1:2] + values[3:len(scputimes._fields) + 2] + fields = [float(x) / CLOCK_TICKS for x in fields] + return scputimes(*fields) + + +def per_cpu_times(): + """Return a list of namedtuple representing the CPU times + for every CPU available on the system. + """ + procfs_path = get_procfs_path() + cpus = [] + with open_binary('%s/stat' % procfs_path) as f: + # get rid of the first line which refers to system wide CPU stats + f.readline() + for line in f: + if line.startswith(b'cpu'): + values = line.split() + fields = values[1:2] + values[3:len(scputimes._fields) + 2] + fields = [float(x) / CLOCK_TICKS for x in fields] + entry = scputimes(*fields) + cpus.append(entry) + return cpus + + +cpu_count_logical = _pslinux_common.cpu_count_logical +cpu_count_physical = _pslinux_common.cpu_count_physical +cpu_stats = _pslinux_common.cpu_stats + + +# ===================================================================== +# --- network +# ===================================================================== + + +def net_if_addrs(): + """Return the addresses associated to each NIC (network interface + card) installed on the system. + """ + raise NotImplementedError("net_if_addrs not implemented on Cygwin") + + +def net_connections(kind='inet'): + """Return system-wide open connections.""" + raise NotImplementedError("net_connections not implemented on Cygwin") + + +def net_io_counters(): + """Return network I/O statistics for every network interface + installed on the system as a dict of raw tuples. + """ + raise NotImplementedError("net_io_counters not implemented on Cygwin") + + +def net_if_stats(): + """Return information about each NIC (network interface card) + installed on the system. + """ + raise NotImplementedError("net_if_stats not implemented on Cygwin") + + +# ===================================================================== +# --- disks +# ===================================================================== + + +def disk_usage(path): + """Return disk usage statistics about the given *path* as a + namedtuple including total, used and free space expressed in bytes + plus the percentage usage. + """ + raise NotImplementedError("disk_usage not implemented on Cygwin") + + +def disk_io_counters(perdisk=False): + """Return disk I/O statistics for every disk installed on the + system as a dict of raw tuples. + """ + # Note: Currently not implemented on Cygwin, but rather than raise + # a NotImplementedError we can return an empty dict here. + return {} + + +def disk_partitions(all=False): + """Return mounted disk partitions as a list of namedtuples.""" + raise NotImplementedError("disk_partitions not implemented on Cygwin") + + +# ===================================================================== +# --- other system functions +# ===================================================================== + + +def users(): + """Return currently connected users as a list of namedtuples.""" + raise NotImplementedError("users not implemented on Cygwin") + + +# ===================================================================== +# --- processes +# ===================================================================== + + +pid_exists = _psposix.pid_exists +pids = _pslinux_common.pids + + +def ppid_map(): + """Obtain a {pid: ppid, ...} dict for all running processes in + one shot. Used to speed up Process.children(). + + Note: Although the psutil._pslinux.ppid_map implementation also + works on Cygwin, it is very slow, as reading the full /proc/[pid]/stat + file is slow on Cygwin. Instead, Cygwin supplies a /proc/[pid]/ppid file + as a faster way to get a process's PPID. + """ + ret = {} + procfs_path = get_procfs_path() + for pid in pids(): + try: + with open_binary("%s/%s/ppid" % (procfs_path, pid)) as f: + data = f.read() + except EnvironmentError as err: + # Note: we should be able to access /stat for all processes + # aka it's unlikely we'll bump into EPERM, which is good. + if err.errno not in (errno.ENOENT, errno.ESRCH): + raise + else: + ret[pid] = int(data.strip()) + return ret + + +class Process(BaseProcess): + """Cygwin process implementation.""" + + @memoize_when_activated + def _parse_stat_file(self): + try: + stats = super(Process, self)._parse_stat_file() + except IOError as err: + # NOTE: On Cygwin, if the stat file exists but reading it raises + # an EINVAL, this indicates that we are probably looking at a + # zombie process (this doesn't happen in all cases--seems it might + # be a bug in Cygwin) + # + # In some cases there is also a race condition where reading the + # file "succeeds" but it is empty. In this case an IndexError is + # raised. + # + # In this case we return some default values based on what Cygwin + # would return if not for this bug + if not (err.errno == errno.EINVAL and + os.path.exists(err.filename)): + raise + + stats = {} + + if stats: + # Might be empty if the stat file was found to be empty while + # trying to read a zombie process + return stats + + # Fallback implementation for broken zombie processes + stats = { + 'name': b'', + 'status': b'Z', + 'ttynr': 0, + 'utime': 0, + 'stime': 0, + 'children_utime': 0, + 'children_stime': 0, + 'create_time': 0, + 'cpu_num': 0 + } + + # It is still possible to read the ppid, pgid, sid, and ctty + for field in ['ppid', 'pgid', 'sid', 'ctty']: + fname = os.path.join(self._procfs_path, str(self.pid), field) + with open_binary(fname) as f: + val = f.read().strip() + try: + val = int(val) + except ValueError: + pass + stats[field] = val + return stats + + @memoize_when_activated + def _read_status_file(self): + """Read /proc/{pid}/status file and return its content. + The return value is cached in case oneshot() ctx manager is + in use. + """ + + # Plagued by the same problem with zombie processes as _parse_stat_file + # In this case we just return an empty string, and any method which + # calls this method should be responsible for providing sensible + # fallbacks in that case + try: + return super(Process, self)._read_status_file() + except IOError as err: + if not (err.errno == errno.EINVAL and + os.path.exists(err.filename)): + raise + + return b'' + + @wrap_exceptions + def ppid(self): + with open_binary("%s/%s/ppid" % (self._procfs_path, self.pid)) as f: + return int(f.read().strip()) + + def terminal(self): + raise NotImplementedError("terminal not implemented on Cygwin") + + def num_ctx_switches(self): + raise NotImplementedError("num_ctx_switches not implemented on Cygwin") + + def num_fds(self): + raise NotImplementedError("num_fds not implemented on Cygwin") + + def num_threads(self): + raise NotImplementedError("num_threads not implemented on Cygwin") + + def memory_info(self): + # This can, in rare cases, be impacted by the same zombie process bug + # as _parse_stat_file + try: + return super(Process, self).memory_info() + except (IOError, IndexError) as err: + if not (isinstance(err, IndexError) or + (err.errno == errno.EINVAL and + os.path.exists(err.filename))): + raise + + # Dummy result + return pmem(*[0 for _ in range(len(pmem._fields))]) + + # For now memory_full_info is just the same as memory_info on Cygwin We + # could obtain info like USS, but it might require Windows system calls + def memory_full_info(self): + return self.memory_info() + + def open_files(self): + raise NotImplementedError("open_files not implemented on Cygwin") + + def connections(self, kind='inet'): + raise NotImplementedError("connections not implemented on Cygwin") + + _create_time_map = {} + + def create_time(self): + # On Cygwin there is a rounding bug in process creation time + # calculation, such that the same process's process creation time + # can be reported differently from time to time (within a delta of + # 1 second). + # Therefore we use a hack: The first time create_time() is called + # for a process we map that process's pid to its creation time + # in _create_time_map above. If another Process with the same pid + # is created it first checks that pid's entry in the map, and if the + # cached creation time is within 1 second it reuses the cached time. + # We can assume that two procs with the same pid and creation time + # within 1 second are the same process. Cygwin will not reuse pids for + # subsequently created processes, so it is is almost impossible to have + # two different processes with the same pid within 1 second + create_time = super(Process, self).create_time() + cached = self._create_time_map.setdefault(self.pid, create_time) + if abs(create_time - cached) <= 1: + return cached + else: + # This would occur if the same PID has been reused some time + # much later (at least more than a second) since we last saw + # a process with this PID. + self._create_time_map[self.pid] = create_time + return create_time + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except TimeoutExpired: + raise TimeoutExpired(timeout, self.pid, self._name) diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index cd5dc3ba44..a1eaa3cd23 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -1346,6 +1346,8 @@ class Process(BaseProcess): @memoize_when_activated def _read_smaps_file(self): + # Note: /proc/[pid]/smaps supported on Linux since 2.6.14, but not + # on Cygwin with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid), buffering=BIGFILE_BUFFERING) as f: return f.read().strip() @@ -1714,15 +1716,3 @@ def num_fds(self): @wrap_exceptions def ppid(self): return int(self._parse_stat_file()['ppid']) - - @wrap_exceptions - def uids(self, _uids_re=re.compile(br'Uid:\t(\d+)\t(\d+)\t(\d+)')): - data = self._read_status_file() - real, effective, saved = _uids_re.findall(data)[0] - return _common.puids(int(real), int(effective), int(saved)) - - @wrap_exceptions - def gids(self, _gids_re=re.compile(br'Gid:\t(\d+)\t(\d+)\t(\d+)')): - data = self._read_status_file() - real, effective, saved = _gids_re.findall(data)[0] - return _common.pgids(int(real), int(effective), int(saved)) diff --git a/psutil/_pslinux_common.py b/psutil/_pslinux_common.py index 837050c524..275949a29b 100644 --- a/psutil/_pslinux_common.py +++ b/psutil/_pslinux_common.py @@ -52,6 +52,13 @@ "P": _common.STATUS_PARKED, } +# These objects get set on "import psutil" from the __init__.py +# file, see: https://github.com/giampaolo/psutil/issues/1402 +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + # ===================================================================== # --- named tuples @@ -193,7 +200,7 @@ def cpu_stats(): break syscalls = 0 return _common.scpustats( - ctx_switches, interrupts, soft_interrupts, syscalls) + ctx_switches or 0, interrupts or 0, soft_interrupts or 0, syscalls) # ===================================================================== @@ -280,6 +287,19 @@ def _assert_alive(self): # incorrect or incomplete result. os.stat('%s/%s' % (self._procfs_path, self.pid)) + # Map field names to indices of fields in the /proc/[pid]/stat file + _stat_map = { + 'status': 0, + 'ppid': 1, + 'ttynr': 4, + 'utime': 11, + 'stime': 12, + 'children_utime': 13, + 'children_stime': 14, + 'create_time': 19, + 'cpu_num': 36 + } + @wrap_exceptions @memoize_when_activated def _parse_stat_file(self): @@ -293,6 +313,11 @@ def _parse_stat_file(self): """ with open_binary("%s/%s/stat" % (self._procfs_path, self.pid)) as f: data = f.read() + + # Can sometimes happen on Cygwin with zombie processes + if not data: + return {} + # Process name is between parentheses. It can contain spaces and # other parentheses. This is taken into account by looking for # the first occurrence of "(" and the last occurence of ")". @@ -300,24 +325,19 @@ def _parse_stat_file(self): name = data[data.find(b'(') + 1:rpar] fields = data[rpar + 2:].split() - ret = {} - ret['name'] = name - ret['status'] = fields[0] - ret['ppid'] = fields[1] - ret['ttynr'] = fields[4] - ret['utime'] = fields[11] - ret['stime'] = fields[12] - ret['children_utime'] = fields[13] - ret['children_stime'] = fields[14] - ret['create_time'] = fields[19] - ret['cpu_num'] = fields[36] + ret = {'name': name} + for field, idx in self._stat_map.items(): + try: + ret[field] = fields[idx] + except IndexError: + pass return ret @wrap_exceptions @memoize_when_activated def _read_status_file(self): - """Read /proc/{pid}/stat file and return its content. + """Read /proc/{pid}/status file and return its content. The return value is cached in case oneshot() ctx manager is in use. """ @@ -426,6 +446,24 @@ def status(self): # XXX is '?' legit? (we're not supposed to return it anyway) return PROC_STATUSES.get(letter, '?') + @wrap_exceptions + def uids(self, _uids_re=re.compile(br'Uid:\s+(\d+)\s+(\d+)\s+(\d+)')): + data = self._read_status_file() + if data: + real, effective, saved = _uids_re.findall(data)[0] + else: + real, effective, saved = (0, 0, 0) + return _common.puids(int(real), int(effective), int(saved)) + + @wrap_exceptions + def gids(self, _gids_re=re.compile(br'Gid:\s+(\d+)\s+(\d+)\s+(\d+)')): + data = self._read_status_file() + if data: + real, effective, saved = _gids_re.findall(data)[0] + else: + real, effective, saved = (0, 0, 0) + return _common.pgids(int(real), int(effective), int(saved)) + @wrap_exceptions def memory_info(self): # ============================================================ diff --git a/psutil/_psutil_cygwin.c b/psutil/_psutil_cygwin.c new file mode 100644 index 0000000000..a4bd828e3e --- /dev/null +++ b/psutil/_psutil_cygwin.c @@ -0,0 +1,77 @@ +#include + +#include "_psutil_common.h" + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef +PsutilMethods[] = { + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + + {NULL, NULL, 0, NULL} +}; + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int +psutil_cygwin_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int +psutil_cygwin_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_cygwin", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_cygwin_traverse, + psutil_cygwin_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_cygwin(void) + +#else +#define INITERROR return + +void init_psutil_cygwin(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_cygwin", PsutilMethods); +#endif + + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); + + if (module == NULL) + INITERROR; +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c index d9a8f6d1d6..a8f837f53b 100644 --- a/psutil/_psutil_posix.c +++ b/psutil/_psutil_posix.c @@ -40,6 +40,9 @@ #include #elif defined(PSUTIL_AIX) #include +#elif defined(PSUTIL_CYGWIN) + #include + #include #endif #include "_psutil_common.h" @@ -64,7 +67,7 @@ psutil_pid_exists(long pid) { // Not what we want. Some platforms have PID 0, some do not. // We decide that at runtime. if (pid == 0) { -#if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD) +#if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD) || defined(PSUTIL_CYGWIN) return 0; #else return 1; diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 3aebbcbd78..45ae99fdf4 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -36,6 +36,7 @@ from socket import SOCK_STREAM import psutil +from psutil import CYGWIN from psutil import MACOS from psutil import POSIX from psutil import SUNOS @@ -942,6 +943,11 @@ def tcp_socketpair(family, addr=("", 0)): raise +# NOTE: Cygwin has a known bug +# (https://cygwin.com/ml/cygwin/2017-01/msg00111.html) with creating a pair +# of UNIX sockets within the same process and having them communicate with +# each other. Therefore any test which uses this should be skipped on Cygwin +# until there is a better solution. def unix_socketpair(name): """Build a pair of UNIX sockets connected to each other through the same UNIX file name. @@ -976,7 +982,7 @@ def create_sockets(): 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: + if POSIX and HAS_CONNECTIONS_UNIX and not CYGWIN: fname1 = unix_socket_path().__enter__() fname2 = unix_socket_path().__enter__() s1, s2 = unix_socketpair(fname1) diff --git a/psutil/tests/test_connections.py b/psutil/tests/test_connections.py index 68eea7845d..53896e90ff 100755 --- a/psutil/tests/test_connections.py +++ b/psutil/tests/test_connections.py @@ -16,6 +16,7 @@ from socket import SOCK_STREAM import psutil +from psutil import CYGWIN from psutil import FREEBSD from psutil import LINUX from psutil import MACOS @@ -145,6 +146,7 @@ def compare_procsys_connections(self, pid, proc_cons, kind='all'): class TestUnconnectedSockets(Base, unittest.TestCase): """Tests sockets which are open but not connected to anything.""" + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") 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: @@ -153,6 +155,7 @@ def test_tcp_v4(self): self.assertEqual(conn.status, psutil.CONN_LISTEN) @unittest.skipIf(not supports_ipv6(), "IPv6 not supported") + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") def test_tcp_v6(self): addr = ("::1", get_free_port()) with closing(bind_socket(AF_INET6, SOCK_STREAM, addr=addr)) as sock: @@ -160,6 +163,7 @@ def test_tcp_v6(self): assert not conn.raddr self.assertEqual(conn.status, psutil.CONN_LISTEN) + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") 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: @@ -168,6 +172,7 @@ def test_udp_v4(self): self.assertEqual(conn.status, psutil.CONN_NONE) @unittest.skipIf(not supports_ipv6(), "IPv6 not supported") + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") def test_udp_v6(self): addr = ("::1", get_free_port()) with closing(bind_socket(AF_INET6, SOCK_DGRAM, addr=addr)) as sock: @@ -176,6 +181,7 @@ def test_udp_v6(self): self.assertEqual(conn.status, psutil.CONN_NONE) @unittest.skipIf(not POSIX, 'POSIX only') + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") def test_unix_tcp(self): with unix_socket_path() as name: with closing(bind_unix_socket(name, type=SOCK_STREAM)) as sock: @@ -184,6 +190,7 @@ def test_unix_tcp(self): self.assertEqual(conn.status, psutil.CONN_NONE) @unittest.skipIf(not POSIX, 'POSIX only') + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") def test_unix_udp(self): with unix_socket_path() as name: with closing(bind_unix_socket(name, type=SOCK_STREAM)) as sock: @@ -204,6 +211,7 @@ class TestConnectedSocketPairs(Base, unittest.TestCase): # On SunOS, even after we close() it, the server socket stays around # in TIME_WAIT state. + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") @unittest.skipIf(SUNOS, "unreliable on SUONS") def test_tcp(self): addr = ("127.0.0.1", get_free_port()) @@ -224,6 +232,7 @@ def test_tcp(self): server.close() client.close() + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") @unittest.skipIf(not POSIX, 'POSIX only') def test_unix(self): with unix_socket_path() as name: @@ -257,6 +266,7 @@ def test_unix(self): server.close() client.close() + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") @skip_on_access_denied(only_if=MACOS) def test_combos(self): def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): @@ -355,6 +365,7 @@ def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): # err self.assertRaises(ValueError, p.connections, kind='???') + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") def test_multi_sockets_filtering(self): with create_sockets() as socks: cons = thisproc.connections(kind='all') @@ -424,6 +435,7 @@ class TestSystemWideConnections(Base, unittest.TestCase): """Tests for net_connections().""" @skip_on_access_denied() + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") def test_it(self): def check(cons, families, types_): AF_UNIX = getattr(socket, 'AF_UNIX', object()) @@ -447,6 +459,7 @@ def check(cons, families, types_): self.assertRaises(ValueError, psutil.net_connections, kind='???') @skip_on_access_denied() + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") def test_multi_socks(self): with create_sockets() as socks: cons = [x for x in psutil.net_connections(kind='all') @@ -455,6 +468,7 @@ def test_multi_socks(self): @skip_on_access_denied() # See: https://travis-ci.org/giampaolo/psutil/jobs/237566297 + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") @unittest.skipIf(MACOS and TRAVIS, "unreliable on MACOS + TRAVIS") def test_multi_sockets_procs(self): # Creates multiple sub processes, each creating different diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index adf7b680a6..ff0a68ef8b 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -19,6 +19,7 @@ from psutil import AIX from psutil import BSD +from psutil import CYGWIN from psutil import FREEBSD from psutil import LINUX from psutil import MACOS @@ -68,7 +69,7 @@ def test_win_service(self): def test_PROCFS_PATH(self): self.assertEqual(hasattr(psutil, "PROCFS_PATH"), - LINUX or SUNOS or AIX) + LINUX or SUNOS or AIX or CYGWIN) def test_win_priority(self): ae = self.assertEqual @@ -149,7 +150,7 @@ def test_proc_rlimit(self): def test_proc_io_counters(self): hasit = hasattr(psutil.Process, "io_counters") - self.assertEqual(hasit, False if MACOS or SUNOS else True) + self.assertEqual(hasit, False if (MACOS or SUNOS or CYGWIN) else True) def test_proc_num_fds(self): self.assertEqual(hasattr(psutil.Process, "num_fds"), POSIX) @@ -167,8 +168,8 @@ def test_proc_cpu_num(self): def test_proc_memory_maps(self): hasit = hasattr(psutil.Process, "memory_maps") - self.assertEqual( - hasit, False if OPENBSD or NETBSD or AIX or MACOS else True) + supported = not any([OPENBSD, NETBSD, AIX, MACOS, CYGWIN]) + self.assertEqual(hasit, supported) # =================================================================== @@ -218,6 +219,7 @@ def test_io_counters(self): for k in psutil.disk_io_counters(perdisk=True): self.assertIsInstance(k, str) + @unittest.skipIf(CYGWIN, "disk_partitions not supported yet on Cygwin") def test_disk_partitions(self): # Duplicate of test_system.py. Keep it anyway. for disk in psutil.disk_partitions(): @@ -228,6 +230,7 @@ def test_disk_partitions(self): @unittest.skipIf(not POSIX, 'POSIX only') @unittest.skipIf(not HAS_CONNECTIONS_UNIX, "can't list UNIX sockets") + @unittest.skipIf(CYGWIN, "net_connections not supported yet on Cygwin") @skip_on_access_denied(only_if=MACOS) def test_net_connections(self): with unix_socket_path() as name: @@ -237,6 +240,7 @@ def test_net_connections(self): for conn in cons: self.assertIsInstance(conn.laddr, str) + @unittest.skipIf(CYGWIN, "net_if_addrs not supported yet on Cygwin") def test_net_if_addrs(self): # Duplicate of test_system.py. Keep it anyway. for ifname, addrs in psutil.net_if_addrs().items(): @@ -246,12 +250,14 @@ def test_net_if_addrs(self): self.assertIsInstance(addr.netmask, (str, type(None))) self.assertIsInstance(addr.broadcast, (str, type(None))) + @unittest.skipIf(CYGWIN, "net_if_stats not supported yet on Cygwin") 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) @unittest.skipIf(not HAS_NET_IO_COUNTERS, 'not supported') + @unittest.skipIf(CYGWIN, "net_io_counters not supported yet on Cygwin") def test_net_io_counters(self): # Duplicate of test_system.py. Keep it anyway. for ifname, _ in psutil.net_io_counters(pernic=True).items(): @@ -273,6 +279,7 @@ def test_sensors_temperatures(self): for unit in units: self.assertIsInstance(unit.label, str) + @unittest.skipIf(CYGWIN, "users not supported yet on Cygwin") def test_users(self): # Duplicate of test_system.py. Keep it anyway. for user in psutil.users(): @@ -385,7 +392,7 @@ def exe(self, ret, proc): if not ret: self.assertEqual(ret, '') else: - assert os.path.isabs(ret), ret + assert os.path.isabs(ret) or (CYGWIN and 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 @@ -507,8 +514,9 @@ def memory_info(self, ret, proc): 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 + if POSIX and not (AIX or CYGWIN) and ret.vms != 0: + # VMS is always supposed to be the highest but some memory + # management implementations may be different (see AIX, Cygwin) for name in ret._fields: if name != 'vms': value = getattr(ret, name) @@ -567,7 +575,9 @@ def connections(self, ret, proc): def cwd(self, ret, proc): if ret: # 'ret' can be None or empty self.assertIsInstance(ret, str) - assert os.path.isabs(ret), ret + # On Cygwin if the process became a zombie before accessing + # the cwd can become '' + assert os.path.isabs(ret) or (CYGWIN and ret == ''), ret try: st = os.stat(ret) except OSError as err: diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py index 29e1e41e60..cacd8483bc 100755 --- a/psutil/tests/test_misc.py +++ b/psutil/tests/test_misc.py @@ -19,6 +19,7 @@ import socket import stat +from psutil import CYGWIN from psutil import LINUX from psutil import POSIX from psutil import WINDOWS @@ -342,6 +343,7 @@ def test_isfile_strict(self): with mock.patch('psutil._common.stat.S_ISREG', return_value=False): assert not isfile_strict(this_file) + @unittest.skipIf(CYGWIN, "swap_memory not supported yet on Cygwin") def test_serialization(self): def check(ret): if json is not None: @@ -698,34 +700,43 @@ def test_executable(self): if not stat.S_IXUSR & os.stat(path)[stat.ST_MODE]: self.fail('%r is not executable' % path) + @unittest.skipIf(CYGWIN, "disk_usage.py not supported yet on Cygwin") def test_disk_usage(self): self.assert_stdout('disk_usage.py') + @unittest.skipIf(CYGWIN, "free.py not supported yet on Cygwin") def test_free(self): self.assert_stdout('free.py') + @unittest.skipIf(CYGWIN, "meminfo.py not supported yet on Cygwin") def test_meminfo(self): self.assert_stdout('meminfo.py') + @unittest.skipIf(CYGWIN, "procinfo.py not supported yet on Cygwin") 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") + @unittest.skipIf(CYGWIN, "users not supported yet on Cygwin") def test_who(self): self.assert_stdout('who.py') + @unittest.skipIf(CYGWIN, "ps.py not supported yet on Cygwin") def test_ps(self): self.assert_stdout('ps.py') + @unittest.skipIf(CYGWIN, "pstree.py not supported yet on Cygwin") def test_pstree(self): self.assert_stdout('pstree.py') + @unittest.skipIf(CYGWIN, "netstat.py not supported yet on Cygwin") def test_netstat(self): self.assert_stdout('netstat.py') # permission denied on travis + @unittest.skipIf(CYGWIN, "ifconfig.py not supported yet on Cygwin") @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") def test_ifconfig(self): self.assert_stdout('ifconfig.py') @@ -1011,6 +1022,7 @@ def tcp_tcp_socketpair(self): self.assertNotEqual(client.getsockname(), addr) @unittest.skipIf(not POSIX, "POSIX only") + @unittest.skipIf(CYGWIN, "num_fds not supported yet on Cygwin") def test_unix_socketpair(self): p = psutil.Process() num_fds = p.num_fds() @@ -1039,7 +1051,7 @@ def test_create_sockets(self): self.assertGreaterEqual(fams[socket.AF_INET], 2) if supports_ipv6(): self.assertGreaterEqual(fams[socket.AF_INET6], 2) - if POSIX and HAS_CONNECTIONS_UNIX: + if POSIX and HAS_CONNECTIONS_UNIX and not CYGWIN: self.assertGreaterEqual(fams[socket.AF_UNIX], 2) self.assertGreaterEqual(types[socket.SOCK_STREAM], 2) self.assertGreaterEqual(types[socket.SOCK_DGRAM], 2) diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py index d24abad38d..27530c8e2d 100755 --- a/psutil/tests/test_posix.py +++ b/psutil/tests/test_posix.py @@ -17,6 +17,7 @@ import psutil from psutil import AIX from psutil import BSD +from psutil import CYGWIN from psutil import LINUX from psutil import MACOS from psutil import OPENBSD @@ -127,26 +128,31 @@ def setUpClass(cls): def tearDownClass(cls): reap_children() + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") def test_ppid(self): ppid_ps = ps('ppid', self.pid) ppid_psutil = psutil.Process(self.pid).ppid() self.assertEqual(ppid_ps, ppid_psutil) + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") def test_uid(self): uid_ps = ps('uid', self.pid) uid_psutil = psutil.Process(self.pid).uids().real self.assertEqual(uid_ps, uid_psutil) + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") def test_gid(self): gid_ps = ps('rgid', self.pid) gid_psutil = psutil.Process(self.pid).gids().real self.assertEqual(gid_ps, gid_psutil) + @unittest.skipIf(CYGWIN, "username not supported yet on Cygwin") def test_username(self): username_ps = ps('user', self.pid) username_psutil = psutil.Process(self.pid).username() self.assertEqual(username_ps, username_psutil) + @unittest.skipIf(CYGWIN, "username not supported yet on Cygwin") 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 @@ -156,6 +162,7 @@ def test_username_no_resolution(self): self.assertEqual(p.username(), str(p.uids().real)) assert fun.called + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") @skip_on_access_denied() @retry_on_failure() def test_rss_memory(self): @@ -166,6 +173,7 @@ def test_rss_memory(self): rss_psutil = psutil.Process(self.pid).memory_info()[0] / 1024 self.assertEqual(rss_ps, rss_psutil) + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") @skip_on_access_denied() @retry_on_failure() def test_vsz_memory(self): @@ -176,6 +184,7 @@ def test_vsz_memory(self): vsz_psutil = psutil.Process(self.pid).memory_info()[1] / 1024 self.assertEqual(vsz_ps, vsz_psutil) + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") def test_name(self): name_ps = ps_name(self.pid) # remove path if there is any, from the command @@ -227,6 +236,7 @@ def test_name_long_cmdline_nsp_exc(self): self.assertRaises(psutil.NoSuchProcess, p.name) @unittest.skipIf(MACOS or BSD, 'ps -o start not available') + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") def test_create_time(self): time_ps = ps('start', self.pid) time_psutil = psutil.Process(self.pid).create_time() @@ -239,6 +249,7 @@ def test_create_time(self): round_time_psutil).strftime("%H:%M:%S") self.assertIn(time_ps, [time_psutil_tstamp, round_time_psutil_tstamp]) + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") def test_exe(self): ps_pathname = ps_name(self.pid) psutil_pathname = psutil.Process(self.pid).exe() @@ -254,6 +265,7 @@ def test_exe(self): adjusted_ps_pathname = ps_pathname[:len(ps_pathname)] self.assertEqual(ps_pathname, adjusted_ps_pathname) + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") def test_cmdline(self): ps_cmdline = ps_args(self.pid) psutil_cmdline = " ".join(psutil.Process(self.pid).cmdline()) @@ -266,11 +278,13 @@ def test_cmdline(self): # AIX has the same issue @unittest.skipIf(SUNOS, "not reliable on SUNOS") @unittest.skipIf(AIX, "not reliable on AIX") + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") def test_nice(self): ps_nice = ps('nice', self.pid) psutil_nice = psutil.Process().nice() self.assertEqual(ps_nice, psutil_nice) + @unittest.skipIf(CYGWIN, "num_fds not supported yet on Cygwin") def test_num_fds(self): # Note: this fails from time to time; I'm keen on thinking # it doesn't mean something is broken @@ -317,6 +331,7 @@ def call(p, attr): class TestSystemAPIs(unittest.TestCase): """Test some system APIs.""" + @unittest.skipIf(CYGWIN, "ps not supported yet on Cygwin") @retry_on_failure() def test_pids(self): # Note: this test might fail if the OS is starting/killing @@ -354,6 +369,7 @@ def test_nic_names(self): # can't find users on APPVEYOR or TRAVIS @unittest.skipIf(APPVEYOR or TRAVIS and not psutil.users(), "unreliable on APPVEYOR or TRAVIS") + @unittest.skipIf(CYGWIN, "users not supported yet on Cygwin") @retry_on_failure() def test_users(self): out = sh("who") @@ -401,6 +417,7 @@ def test_os_waitpid_bad_ret_status(self): # AIX can return '-' in df output instead of numbers, e.g. for /proc @unittest.skipIf(AIX, "unreliable on AIX") + @unittest.skipIf(CYGWIN, "disk_partitions not supported yet on Cygwin") def test_disk_usage(self): def df(device): out = sh("df -k %s" % device).strip() diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index a4b738eeb2..f63fc9b92b 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -24,6 +24,7 @@ from psutil import AIX from psutil import BSD +from psutil import CYGWIN from psutil import LINUX from psutil import MACOS from psutil import NETBSD @@ -262,10 +263,19 @@ def test_cpu_times_2(self): # 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: + # + # On Cygwin there is enough overhead involved in reading + # /proc/stat as compared to os.times() that the tolerance + # needs to be quite high especially on a system under load + if CYGWIN: + tol = 1.0 + else: + tol = 0.1 + + if (max([user_time, utime]) - min([user_time, utime])) > tol: self.fail("expected: %s, found: %s" % (utime, user_time)) - if (max([kernel_time, ktime]) - min([kernel_time, ktime])) > 0.1: + if (max([kernel_time, ktime]) - min([kernel_time, ktime])) > tol: self.fail("expected: %s, found: %s" % (ktime, kernel_time)) @unittest.skipIf(not HAS_PROC_CPU_NUM, "not supported") @@ -286,8 +296,9 @@ def test_create_time(self): # 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. + # This check is not reliable on Cygwin difference = abs(create_time - now) - if difference > 2: + if not CYGWIN and difference > 2: self.fail("expected: %s, found: %s, difference: %s" % (now, create_time, difference)) @@ -296,6 +307,7 @@ def test_create_time(self): @unittest.skipIf(not POSIX, 'POSIX only') @unittest.skipIf(TRAVIS, 'not reliable on TRAVIS') + @unittest.skipIf(CYGWIN, "terminal not supported yet on Cygwin") def test_terminal(self): terminal = psutil.Process().terminal() if sys.stdin.isatty() or sys.stdout.isatty(): @@ -503,6 +515,7 @@ def test_rlimit_infinity_value(self): self.assertEqual(psutil.RLIM_INFINITY, hard) p.rlimit(psutil.RLIMIT_FSIZE, (soft, hard)) + @unittest.skipIf(CYGWIN, "num_threads not supported yet on Cygwin") 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, @@ -709,6 +722,13 @@ def test_exe(self): def test_cmdline(self): cmdline = [PYTHON_EXE, "-c", "import time; time.sleep(60)"] sproc = get_test_subprocess(cmdline) + + # On some Python versions under Cygwin sys.executable includes + # the .exe extension, whereas Process.cmdline() does not, so normalize + # that difference here: + if CYGWIN: + cmdline[0] = os.path.splitext(cmdline[0])[0] + try: self.assertEqual(' '.join(psutil.Process(sproc.pid).cmdline()), ' '.join(cmdline)) @@ -725,7 +745,7 @@ def test_cmdline(self): raise def test_name(self): - sproc = get_test_subprocess(PYTHON_EXE) + sproc = get_test_subprocess() name = psutil.Process(sproc.pid).name().lower() pyexe = os.path.basename(os.path.realpath(sys.executable)).lower() assert pyexe.startswith(name), (pyexe, name) @@ -764,6 +784,7 @@ def rm(): os.path.normcase(funky_path)) @unittest.skipIf(not POSIX, 'POSIX only') + @unittest.skipIf(CYGWIN, "uids not supported yet on Cygwin") def test_uids(self): p = psutil.Process() real, effective, saved = p.uids() @@ -778,6 +799,7 @@ def test_uids(self): self.assertEqual(os.getresuid(), p.uids()) @unittest.skipIf(not POSIX, 'POSIX only') + @unittest.skipIf(CYGWIN, "gids not supported yet on Cygwin") def test_gids(self): p = psutil.Process() real, effective, saved = p.gids() @@ -836,6 +858,7 @@ def test_status(self): p = psutil.Process() self.assertEqual(p.status(), psutil.STATUS_RUNNING) + @unittest.skipIf(CYGWIN, "username not supported yet on Cygwin") def test_username(self): sproc = get_test_subprocess() p = psutil.Process(sproc.pid) @@ -935,6 +958,7 @@ def test_cpu_affinity_all_combinations(self): @unittest.skipIf(BSD, "broken on BSD") # can't find any process file on Appveyor @unittest.skipIf(APPVEYOR, "unreliable on APPVEYOR") + @unittest.skipIf(CYGWIN, "open_files not supported yet on Cygwin") def test_open_files(self): # current process p = psutil.Process() @@ -974,6 +998,7 @@ def test_open_files(self): @unittest.skipIf(BSD, "broken on BSD") # can't find any process file on Appveyor @unittest.skipIf(APPVEYOR, "unreliable on APPVEYOR") + @unittest.skipIf(CYGWIN, "open_files not supported yet on Cygwin") def test_open_files_2(self): # test fd and path fields with open(TESTFN, 'w') as fileobj: @@ -996,6 +1021,7 @@ def test_open_files_2(self): self.assertNotIn(fileobj.name, p.open_files()) @unittest.skipIf(not POSIX, 'POSIX only') + @unittest.skipIf(CYGWIN, "num_fds not supported yet on Cygwin") def test_num_fds(self): p = psutil.Process() start = p.num_fds() @@ -1011,6 +1037,7 @@ def test_num_fds(self): @skip_on_not_implemented(only_if=LINUX) @unittest.skipIf(OPENBSD or NETBSD, "not reliable on OPENBSD & NETBSD") + @unittest.skipIf(CYGWIN, "num_ctx_switches not supported yet on Cygwin") def test_num_ctx_switches(self): p = psutil.Process() before = sum(p.num_ctx_switches()) @@ -1046,6 +1073,8 @@ def test_parent(self): p = psutil.Process(sproc.pid) self.assertEqual(p.parent().pid, this_parent) + @unittest.skipIf(CYGWIN, 'not a meaningful test on Cygwin') + def test_lowest_pid_parent(self): lowest_pid = psutil.pids()[0] self.assertIsNone(psutil.Process(lowest_pid).parent()) @@ -1068,7 +1097,7 @@ def test_parents(self): self.assertEqual(p1.parents()[0], psutil.Process()) self.assertEqual(p2.parents()[0], p1) self.assertEqual(p2.parents()[1], psutil.Process()) - if POSIX: + if POSIX and not CYGWIN: lowest_pid = psutil.pids()[0] self.assertEqual(p1.parents()[-1].pid, lowest_pid) self.assertEqual(p2.parents()[-1].pid, lowest_pid) @@ -1111,8 +1140,17 @@ def test_children_duplicates(self): 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) + for pid, _ in sorted(table.items(), key=lambda x: x[1], reverse=True): + try: + # Make sure the process can be accessed and actually exists + # E.g. on Cygwin PID=1 is the default PPID for initial + # processes, but does not represent an actual process + p = psutil.Process(pid) + except (psutil.AccessDenied, psutil.NoSuchProcess): + continue + + break + try: c = p.children(recursive=True) except psutil.AccessDenied: # windows @@ -1154,9 +1192,12 @@ def test_as_dict(self): 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') + + if not CYGWIN: + # Skip on CYGWIN: connections not supported yet + 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, diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index 75e65b0fbd..c95ef3a4ab 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -21,6 +21,7 @@ import psutil from psutil import AIX from psutil import BSD +from psutil import CYGWIN from psutil import FREEBSD from psutil import LINUX from psutil import MACOS @@ -199,6 +200,7 @@ def test_virtual_memory(self): self.fail("%r > total (total=%s, %s=%s)" % (name, mem.total, name, value)) + @unittest.skipIf(CYGWIN, "swap_memory not supported yet on Cygwin") def test_swap_memory(self): mem = psutil.swap_memory() self.assertEqual( @@ -444,6 +446,7 @@ def test_per_cpu_times_percent_negative(self): for percent in cpu: self._test_cpu_percent(percent, None, None) + @unittest.skipIf(CYGWIN, "disk_usage not supported yet on Cygwin") def test_disk_usage(self): usage = psutil.disk_usage(os.getcwd()) self.assertEqual(usage._fields, ('total', 'used', 'free', 'percent')) @@ -477,9 +480,11 @@ def test_disk_usage_unicode(self): with self.assertRaises(UnicodeEncodeError): psutil.disk_usage(TESTFN_UNICODE) + @unittest.skipIf(CYGWIN, "disk_usage not supported yet on Cygwin") def test_disk_usage_bytes(self): psutil.disk_usage(b'.') + @unittest.skipIf(CYGWIN, "disk_partitions not supported yet on Cygwin") def test_disk_partitions(self): # all = False ls = psutil.disk_partitions(all=False) @@ -539,6 +544,7 @@ def find_mount_point(path): psutil.disk_usage(mount) @unittest.skipIf(not HAS_NET_IO_COUNTERS, 'not supported') + @unittest.skipIf(CYGWIN, "net_io_counters not supported yet on Cygwin") def test_net_io_counters(self): def check_ntuple(nt): self.assertEqual(nt[0], nt.bytes_sent) @@ -577,6 +583,7 @@ def test_net_io_counters_no_nics(self): self.assertEqual(psutil.net_io_counters(pernic=True), {}) assert m.called + @unittest.skipIf(CYGWIN, "net_if_addrs not supported yet on Cygwin") def test_net_if_addrs(self): nics = psutil.net_if_addrs() assert nics, nics @@ -655,6 +662,7 @@ def test_net_if_addrs_mac_null_bytes(self): self.assertEqual(addr.address, '06-3d-29-00-00-00') @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") # raises EPERM + @unittest.skipIf(CYGWIN, "net_if_stats not supported yet on Cygwin") def test_net_if_stats(self): nics = psutil.net_if_stats() assert nics, nics @@ -682,6 +690,7 @@ def test_net_if_stats_enodev(self): @unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'), '/proc/diskstats not available on this linux version') + @unittest.skipIf(CYGWIN, "disk_io_counters not supported yet on Cygwin") @unittest.skipIf(APPVEYOR and psutil.disk_io_counters() is None, "unreliable on APPVEYOR") # no visible disks def test_disk_io_counters(self): @@ -724,6 +733,7 @@ def test_disk_io_counters_no_disks(self): # can't find users on APPVEYOR or TRAVIS @unittest.skipIf(APPVEYOR or TRAVIS and not psutil.users(), "unreliable on APPVEYOR or TRAVIS") + @unittest.skipIf(CYGWIN, "users not supported yet on Cygwin") def test_users(self): users = psutil.users() self.assertNotEqual(users, []) diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py index f7115e3d4c..1e67e0c571 100755 --- a/psutil/tests/test_unicode.py +++ b/psutil/tests/test_unicode.py @@ -58,6 +58,7 @@ from contextlib import closing from psutil import BSD +from psutil import CYGWIN from psutil import MACOS from psutil import OPENBSD from psutil import POSIX @@ -159,6 +160,10 @@ def tearDown(self): def expect_exact_path_match(self): raise NotImplementedError("must be implemented in subclass") + # NOTE: The following three tests are not reliable on Cygwin, due to the + # difficulty of retrieving the command name of zombie processes (which + # just return '') + @unittest.skipIf(CYGWIN, "not reliable on Cygwin") def test_proc_exe(self): subp = get_test_subprocess(cmd=[self.funky_name]) p = psutil.Process(subp.pid) @@ -167,6 +172,7 @@ def test_proc_exe(self): if self.expect_exact_path_match(): self.assertEqual(exe, self.funky_name) + @unittest.skipIf(CYGWIN, "not reliable on Cygwin") def test_proc_name(self): subp = get_test_subprocess(cmd=[self.funky_name]) if WINDOWS: @@ -183,6 +189,7 @@ def test_proc_name(self): if self.expect_exact_path_match(): self.assertEqual(name, os.path.basename(self.funky_name)) + @unittest.skipIf(CYGWIN, "not reliable on Cygwin") def test_proc_cmdline(self): subp = get_test_subprocess(cmd=[self.funky_name]) p = psutil.Process(subp.pid) @@ -203,6 +210,7 @@ def test_proc_cwd(self): if self.expect_exact_path_match(): self.assertEqual(cwd, dname) + @unittest.skipIf(CYGWIN, "open_files not supported yet on Cygwin") def test_proc_open_files(self): p = psutil.Process() start = set(p.open_files()) @@ -218,6 +226,7 @@ def test_proc_open_files(self): os.path.normcase(self.funky_name)) @unittest.skipIf(not POSIX, "POSIX only") + @unittest.skipIf(CYGWIN, "connections not supported yet on Cygwin") def test_proc_connections(self): suffix = os.path.basename(self.funky_name) with unix_socket_path(suffix=suffix) as name: @@ -237,6 +246,7 @@ def test_proc_connections(self): @unittest.skipIf(not POSIX, "POSIX only") @unittest.skipIf(not HAS_CONNECTIONS_UNIX, "can't list UNIX sockets") + @unittest.skipIf(CYGWIN, "net_connections not supported yet on Cygwin") @skip_on_access_denied() def test_net_connections(self): def find_sock(cons): @@ -262,6 +272,7 @@ def find_sock(cons): self.assertIsInstance(conn.laddr, str) self.assertEqual(conn.laddr, name) + @unittest.skipIf(CYGWIN, "disk_usage not supported yet on Cygwin") def test_disk_usage(self): dname = self.funky_name + "2" self.addCleanup(safe_rmpath, dname) diff --git a/setup.py b/setup.py index 465a9b9e33..0b69a89707 100755 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ from _common import AIX # NOQA from _common import BSD # NOQA +from _common import CYGWIN # NOQA from _common import FREEBSD # NOQA from _common import LINUX # NOQA from _common import MACOS # NOQA @@ -253,6 +254,12 @@ def get_ethtool_macro(): libraries=['perfstat'], define_macros=macros) +elif CYGWIN: + macros.append(("PSUTIL_CYGWIN", 1)) + ext = Extension( + 'psutil._psutil_cygwin', + sources=sources + ['psutil/_psutil_cygwin.c'], + define_macros=macros) else: sys.exit('platform %s is not supported' % sys.platform)