diff --git a/Makefile b/Makefile index a8274a649..c6b002a60 100644 --- a/Makefile +++ b/Makefile @@ -125,8 +125,8 @@ install-pip: ## Install pip (no-op if already installed). setup-dev-env: ## Install GIT hooks, pip, test deps (also upgrades them). ${MAKE} install-git-hooks ${MAKE} install-pip - $(PYTHON) -m pip install $(INSTALL_OPTS) --upgrade --trusted-host files.pythonhosted.org pip - $(PYTHON) -m pip install $(INSTALL_OPTS) --upgrade --trusted-host files.pythonhosted.org $(PY_DEPS) + $(PYTHON) -m pip install $(INSTALL_OPTS) --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade pip + $(PYTHON) -m pip install $(INSTALL_OPTS) --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade $(PY_DEPS) # =================================================================== # Tests diff --git a/psutil/__init__.py b/psutil/__init__.py index 68f3c7ca9..49ab94d50 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -404,7 +404,7 @@ def __str__(self): pass if self._exitcode not in (_SENTINEL, None): info["exitcode"] = self._exitcode - if self._create_time: + if self._create_time is not None: info['started'] = _pprint_secs(self._create_time) return "%s.%s(%s)" % ( self.__class__.__module__, @@ -418,6 +418,20 @@ def __eq__(self, other): # on PID and creation time. if not isinstance(other, Process): return NotImplemented + if OPENBSD or NETBSD: # pragma: no cover + # Zombie processes on Open/NetBSD have a creation time of + # 0.0. This covers the case when a process started normally + # (so it has a ctime), then it turned into a zombie. It's + # important to do this because is_running() depends on + # __eq__. + pid1, ctime1 = self._ident + pid2, ctime2 = other._ident + if pid1 == pid2: + if ctime1 and not ctime2: + try: + return self.status() == STATUS_ZOMBIE + except Error: + pass return self._ident == other._ident def __ne__(self, other): diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 1cb9fffde..62cef7633 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -43,6 +43,8 @@ from psutil import AIX from psutil import LINUX from psutil import MACOS +from psutil import NETBSD +from psutil import OPENBSD from psutil import POSIX from psutil import SUNOS from psutil import WINDOWS @@ -959,7 +961,12 @@ def assertProcessGone(self, proc): def assertProcessZombie(self, proc): # A zombie process should always be instantiable. - psutil.Process(proc.pid) + clone = psutil.Process(proc.pid) + # Cloned zombie on Open/NetBSD has null creation time, see: + # https://github.com/giampaolo/psutil/issues/2287 + self.assertEqual(proc, clone) + if not (OPENBSD or NETBSD): + self.assertEqual(hash(proc), hash(clone)) # Its status always be querable. self.assertEqual(proc.status(), psutil.STATUS_ZOMBIE) # It should be considered 'running'. diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index 9e81e2f5d..80e4988d5 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -72,6 +72,11 @@ def test_process_iter(self): p.wait() self.assertNotIn(sproc.pid, [x.pid for x in psutil.process_iter()]) + # assert there are no duplicates + ls = [x for x in psutil.process_iter()] + self.assertEqual(sorted(ls, key=lambda x: x.pid), + sorted(set(ls), key=lambda x: x.pid)) + with mock.patch('psutil.Process', side_effect=psutil.NoSuchProcess(os.getpid())): self.assertEqual(list(psutil.process_iter()), [])