| #!/usr/bin/env python3 |
| |
| # 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 testing utils.""" |
| |
| import collections |
| import errno |
| import os |
| import socket |
| import stat |
| import subprocess |
| from unittest import mock |
| |
| import psutil |
| import tests |
| from psutil import FREEBSD |
| from psutil import NETBSD |
| from psutil import POSIX |
| from psutil._common import open_binary |
| from psutil._common import open_text |
| from psutil._common import supports_ipv6 |
| |
| from . import HAS_NET_CONNECTIONS_UNIX |
| from . import PYTHON_EXE |
| from . import PYTHON_EXE_ENV |
| from . import PsutilTestCase |
| from . import bind_socket |
| from . import bind_unix_socket |
| from . import call_until |
| from . import chdir |
| from . import create_sockets |
| from . import filter_proc_net_connections |
| from . import get_free_port |
| from . import is_namedtuple |
| from . import process_namespace |
| from . import pytest |
| from . import reap_children |
| from . import retry |
| from . import safe_mkdir |
| from . import safe_rmpath |
| from . import system_namespace |
| from . import tcp_socketpair |
| from . import terminate |
| from . import unix_socketpair |
| from . import wait_for_file |
| from . import wait_for_pid |
| |
| # =================================================================== |
| # --- Unit tests for test utilities. |
| # =================================================================== |
| |
| |
| class TestRetryDecorator(PsutilTestCase): |
| @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 # noqa: B018 |
| return 1 |
| |
| queue = list(range(3)) |
| assert foo() == 1 |
| assert 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 # noqa: B018 |
| return 1 |
| |
| queue = list(range(6)) |
| with pytest.raises(ZeroDivisionError): |
| foo() |
| assert sleep.call_count == 5 |
| |
| @mock.patch('time.sleep') |
| def test_exception_arg(self, sleep): |
| @retry(exception=ValueError, interval=1) |
| def foo(): |
| raise TypeError |
| |
| with pytest.raises(TypeError): |
| foo() |
| assert 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 # noqa: B018 |
| |
| with pytest.raises(ZeroDivisionError): |
| foo() |
| assert 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 # noqa: B018 |
| |
| with pytest.raises(ZeroDivisionError): |
| foo() |
| assert sleep.call_count == 5 |
| |
| @mock.patch('time.sleep') |
| def test_retries_and_timeout_args(self, sleep): |
| with pytest.raises(ValueError): |
| retry(retries=5, timeout=1) |
| |
| |
| class TestSyncTestUtils(PsutilTestCase): |
| def test_wait_for_pid(self): |
| wait_for_pid(os.getpid()) |
| nopid = max(psutil.pids()) + 99999 |
| with mock.patch('tests.retry.__iter__', return_value=iter([0])): |
| with pytest.raises(psutil.NoSuchProcess): |
| wait_for_pid(nopid) |
| |
| def test_wait_for_file(self): |
| testfn = self.get_testfn() |
| 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): |
| testfn = self.get_testfn() |
| 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): |
| testfn = self.get_testfn() |
| with mock.patch('tests.retry.__iter__', return_value=iter([0])): |
| with pytest.raises(OSError): |
| wait_for_file(testfn) |
| |
| def test_wait_for_file_no_delete(self): |
| testfn = self.get_testfn() |
| 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): |
| call_until(lambda: 1) |
| # TODO: test for timeout |
| |
| |
| class TestFSTestUtils(PsutilTestCase): |
| def test_open_text(self): |
| with open_text(__file__) as f: |
| assert f.mode == 'r' |
| |
| def test_open_binary(self): |
| with open_binary(__file__) as f: |
| assert f.mode == 'rb' |
| |
| def test_safe_mkdir(self): |
| testfn = self.get_testfn() |
| 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 |
| testfn = self.get_testfn() |
| 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( |
| 'tests.os.stat', side_effect=OSError(errno.EINVAL, "") |
| ) as m: |
| with pytest.raises(OSError): |
| safe_rmpath(testfn) |
| assert m.called |
| |
| def test_chdir(self): |
| testfn = self.get_testfn() |
| base = os.getcwd() |
| os.mkdir(testfn) |
| with chdir(testfn): |
| assert os.getcwd() == os.path.join(base, testfn) |
| assert os.getcwd() == base |
| |
| |
| class TestProcessUtils(PsutilTestCase): |
| def test_reap_children(self): |
| subp = self.spawn_subproc() |
| p = psutil.Process(subp.pid) |
| assert p.is_running() |
| reap_children() |
| assert not p.is_running() |
| assert not tests._pids_started |
| assert not tests._subprocesses_started |
| |
| def test_spawn_children_pair(self): |
| child, grandchild = self.spawn_children_pair() |
| assert child.pid != grandchild.pid |
| assert child.is_running() |
| assert grandchild.is_running() |
| children = psutil.Process().children() |
| assert children == [child] |
| children = psutil.Process().children(recursive=True) |
| assert len(children) == 2 |
| assert child in children |
| assert grandchild in children |
| assert child.ppid() == os.getpid() |
| assert grandchild.ppid() == child.pid |
| |
| terminate(child) |
| assert not child.is_running() |
| assert grandchild.is_running() |
| |
| terminate(grandchild) |
| assert not grandchild.is_running() |
| |
| @pytest.mark.skipif(not POSIX, reason="POSIX only") |
| def test_spawn_zombie(self): |
| _parent, zombie = self.spawn_zombie() |
| assert zombie.status() == psutil.STATUS_ZOMBIE |
| |
| def test_terminate(self): |
| # by subprocess.Popen |
| p = self.spawn_subproc() |
| terminate(p) |
| self.assert_pid_gone(p.pid) |
| terminate(p) |
| # by psutil.Process |
| p = psutil.Process(self.spawn_subproc().pid) |
| terminate(p) |
| self.assert_pid_gone(p.pid) |
| terminate(p) |
| # by psutil.Popen |
| cmd = [ |
| PYTHON_EXE, |
| "-c", |
| "import time; [time.sleep(0.1) for x in range(100)];", |
| ] |
| p = psutil.Popen( |
| cmd, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| env=PYTHON_EXE_ENV, |
| ) |
| terminate(p) |
| self.assert_pid_gone(p.pid) |
| terminate(p) |
| # by PID |
| pid = self.spawn_subproc().pid |
| terminate(pid) |
| self.assert_pid_gone(p.pid) |
| terminate(pid) |
| # zombie |
| if POSIX: |
| parent, zombie = self.spawn_zombie() |
| terminate(parent) |
| terminate(zombie) |
| self.assert_pid_gone(parent.pid) |
| self.assert_pid_gone(zombie.pid) |
| |
| |
| class TestNetUtils(PsutilTestCase): |
| def bind_socket(self): |
| port = get_free_port() |
| with bind_socket(addr=('', port)) as s: |
| assert s.getsockname()[1] == port |
| |
| @pytest.mark.skipif(not POSIX, reason="POSIX only") |
| def test_bind_unix_socket(self): |
| name = self.get_testfn() |
| with bind_unix_socket(name) as sock: |
| assert sock.family == socket.AF_UNIX |
| assert sock.type == socket.SOCK_STREAM |
| assert sock.getsockname() == name |
| assert os.path.exists(name) |
| assert stat.S_ISSOCK(os.stat(name).st_mode) |
| # UDP |
| name = self.get_testfn() |
| with bind_unix_socket(name, type=socket.SOCK_DGRAM) as sock: |
| assert sock.type == socket.SOCK_DGRAM |
| |
| def test_tcp_socketpair(self): |
| addr = ("127.0.0.1", get_free_port()) |
| server, client = tcp_socketpair(socket.AF_INET, addr=addr) |
| with server, client: |
| # Ensure they are connected and the positions are correct. |
| assert server.getsockname() == addr |
| assert client.getpeername() == addr |
| assert client.getsockname() != addr |
| |
| @pytest.mark.skipif(not POSIX, reason="POSIX only") |
| @pytest.mark.skipif( |
| NETBSD or FREEBSD, reason="/var/run/log UNIX socket opened by default" |
| ) |
| @pytest.mark.skipif( |
| not HAS_NET_CONNECTIONS_UNIX, reason="can't list UNIX sockets" |
| ) |
| def test_unix_socketpair(self): |
| p = psutil.Process() |
| num_fds = p.num_fds() |
| assert not filter_proc_net_connections(p.net_connections(kind='unix')) |
| name = self.get_testfn() |
| server, client = unix_socketpair(name) |
| try: |
| assert os.path.exists(name) |
| assert stat.S_ISSOCK(os.stat(name).st_mode) |
| assert p.num_fds() - num_fds == 2 |
| assert ( |
| len( |
| filter_proc_net_connections(p.net_connections(kind='unix')) |
| ) |
| == 2 |
| ) |
| assert server.getsockname() == name |
| assert 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 |
| assert fams[socket.AF_INET] >= 2 |
| if supports_ipv6(): |
| assert fams[socket.AF_INET6] >= 2 |
| if POSIX and HAS_NET_CONNECTIONS_UNIX: |
| assert fams[socket.AF_UNIX] >= 2 |
| assert types[socket.SOCK_STREAM] >= 2 |
| assert types[socket.SOCK_DGRAM] >= 2 |
| |
| |
| class TestTestingUtils(PsutilTestCase): |
| def test_process_namespace(self): |
| p = psutil.Process() |
| ns = process_namespace(p) |
| ns.test() |
| fun = next(x for x in ns.iter(ns.getters) if x[1] == 'ppid')[0] |
| assert fun() == p.ppid() |
| |
| def test_system_namespace(self): |
| ns = system_namespace() |
| fun = next(x for x in ns.iter(ns.getters) if x[1] == 'net_if_addrs')[0] |
| assert fun() == psutil.net_if_addrs() |
| |
| |
| class TestOtherUtils(PsutilTestCase): |
| def test_is_namedtuple(self): |
| assert is_namedtuple(collections.namedtuple('foo', 'a b c')(1, 2, 3)) |
| assert not is_namedtuple(tuple()) |