You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			868 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			868 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
| #!/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.
 | |
| 
 | |
| """Miscellaneous tests."""
 | |
| 
 | |
| import collections
 | |
| import contextlib
 | |
| import io
 | |
| import json
 | |
| import os
 | |
| import pickle
 | |
| import socket
 | |
| import sys
 | |
| from unittest import mock
 | |
| 
 | |
| import psutil
 | |
| import psutil.tests
 | |
| from psutil import WINDOWS
 | |
| from psutil._common import bcat
 | |
| from psutil._common import cat
 | |
| from psutil._common import debug
 | |
| from psutil._common import isfile_strict
 | |
| from psutil._common import memoize
 | |
| from psutil._common import memoize_when_activated
 | |
| from psutil._common import parse_environ_block
 | |
| from psutil._common import supports_ipv6
 | |
| from psutil._common import wrap_numbers
 | |
| from psutil.tests import HAS_NET_IO_COUNTERS
 | |
| from psutil.tests import PsutilTestCase
 | |
| from psutil.tests import process_namespace
 | |
| from psutil.tests import pytest
 | |
| from psutil.tests import reload_module
 | |
| from psutil.tests import system_namespace
 | |
| 
 | |
| # ===================================================================
 | |
| # --- Test classes' repr(), str(), ...
 | |
| # ===================================================================
 | |
| 
 | |
| 
 | |
| class TestSpecialMethods(PsutilTestCase):
 | |
|     def test_check_pid_range(self):
 | |
|         with pytest.raises(OverflowError):
 | |
|             psutil._psplatform.cext.check_pid_range(2**128)
 | |
|         with pytest.raises(psutil.NoSuchProcess):
 | |
|             psutil.Process(2**128)
 | |
| 
 | |
|     def test_process__repr__(self, func=repr):
 | |
|         p = psutil.Process(self.spawn_subproc().pid)
 | |
|         r = func(p)
 | |
|         assert "psutil.Process" in r
 | |
|         assert f"pid={p.pid}" in r
 | |
|         assert f"name='{p.name()}'" in r.replace("name=u'", "name='")
 | |
|         assert "status=" in r
 | |
|         assert "exitcode=" not in r
 | |
|         p.terminate()
 | |
|         p.wait()
 | |
|         r = func(p)
 | |
|         assert "status='terminated'" in r
 | |
|         assert "exitcode=" in r
 | |
| 
 | |
|         with mock.patch.object(
 | |
|             psutil.Process,
 | |
|             "name",
 | |
|             side_effect=psutil.ZombieProcess(os.getpid()),
 | |
|         ):
 | |
|             p = psutil.Process()
 | |
|             r = func(p)
 | |
|             assert f"pid={p.pid}" in r
 | |
|             assert "status='zombie'" in r
 | |
|             assert "name=" not in r
 | |
|         with mock.patch.object(
 | |
|             psutil.Process,
 | |
|             "name",
 | |
|             side_effect=psutil.NoSuchProcess(os.getpid()),
 | |
|         ):
 | |
|             p = psutil.Process()
 | |
|             r = func(p)
 | |
|             assert f"pid={p.pid}" in r
 | |
|             assert "terminated" in r
 | |
|             assert "name=" not in r
 | |
|         with mock.patch.object(
 | |
|             psutil.Process,
 | |
|             "name",
 | |
|             side_effect=psutil.AccessDenied(os.getpid()),
 | |
|         ):
 | |
|             p = psutil.Process()
 | |
|             r = func(p)
 | |
|             assert f"pid={p.pid}" in r
 | |
|             assert "name=" not in r
 | |
| 
 | |
|     def test_process__str__(self):
 | |
|         self.test_process__repr__(func=str)
 | |
| 
 | |
|     def test_error__repr__(self):
 | |
|         assert repr(psutil.Error()) == "psutil.Error()"
 | |
| 
 | |
|     def test_error__str__(self):
 | |
|         assert str(psutil.Error()) == ""
 | |
| 
 | |
|     def test_no_such_process__repr__(self):
 | |
|         assert (
 | |
|             repr(psutil.NoSuchProcess(321))
 | |
|             == "psutil.NoSuchProcess(pid=321, msg='process no longer exists')"
 | |
|         )
 | |
|         assert (
 | |
|             repr(psutil.NoSuchProcess(321, name="name", msg="msg"))
 | |
|             == "psutil.NoSuchProcess(pid=321, name='name', msg='msg')"
 | |
|         )
 | |
| 
 | |
|     def test_no_such_process__str__(self):
 | |
|         assert (
 | |
|             str(psutil.NoSuchProcess(321))
 | |
|             == "process no longer exists (pid=321)"
 | |
|         )
 | |
|         assert (
 | |
|             str(psutil.NoSuchProcess(321, name="name", msg="msg"))
 | |
|             == "msg (pid=321, name='name')"
 | |
|         )
 | |
| 
 | |
|     def test_zombie_process__repr__(self):
 | |
|         assert (
 | |
|             repr(psutil.ZombieProcess(321))
 | |
|             == 'psutil.ZombieProcess(pid=321, msg="PID still '
 | |
|             'exists but it\'s a zombie")'
 | |
|         )
 | |
|         assert (
 | |
|             repr(psutil.ZombieProcess(321, name="name", ppid=320, msg="foo"))
 | |
|             == "psutil.ZombieProcess(pid=321, ppid=320, name='name',"
 | |
|             " msg='foo')"
 | |
|         )
 | |
| 
 | |
|     def test_zombie_process__str__(self):
 | |
|         assert (
 | |
|             str(psutil.ZombieProcess(321))
 | |
|             == "PID still exists but it's a zombie (pid=321)"
 | |
|         )
 | |
|         assert (
 | |
|             str(psutil.ZombieProcess(321, name="name", ppid=320, msg="foo"))
 | |
|             == "foo (pid=321, ppid=320, name='name')"
 | |
|         )
 | |
| 
 | |
|     def test_access_denied__repr__(self):
 | |
|         assert repr(psutil.AccessDenied(321)) == "psutil.AccessDenied(pid=321)"
 | |
|         assert (
 | |
|             repr(psutil.AccessDenied(321, name="name", msg="msg"))
 | |
|             == "psutil.AccessDenied(pid=321, name='name', msg='msg')"
 | |
|         )
 | |
| 
 | |
|     def test_access_denied__str__(self):
 | |
|         assert str(psutil.AccessDenied(321)) == "(pid=321)"
 | |
|         assert (
 | |
|             str(psutil.AccessDenied(321, name="name", msg="msg"))
 | |
|             == "msg (pid=321, name='name')"
 | |
|         )
 | |
| 
 | |
|     def test_timeout_expired__repr__(self):
 | |
|         assert (
 | |
|             repr(psutil.TimeoutExpired(5))
 | |
|             == "psutil.TimeoutExpired(seconds=5, msg='timeout after 5"
 | |
|             " seconds')"
 | |
|         )
 | |
|         assert (
 | |
|             repr(psutil.TimeoutExpired(5, pid=321, name="name"))
 | |
|             == "psutil.TimeoutExpired(pid=321, name='name', seconds=5, "
 | |
|             "msg='timeout after 5 seconds')"
 | |
|         )
 | |
| 
 | |
|     def test_timeout_expired__str__(self):
 | |
|         assert str(psutil.TimeoutExpired(5)) == "timeout after 5 seconds"
 | |
|         assert (
 | |
|             str(psutil.TimeoutExpired(5, pid=321, name="name"))
 | |
|             == "timeout after 5 seconds (pid=321, name='name')"
 | |
|         )
 | |
| 
 | |
|     def test_process__eq__(self):
 | |
|         p1 = psutil.Process()
 | |
|         p2 = psutil.Process()
 | |
|         assert p1 == p2
 | |
|         p2._ident = (0, 0)
 | |
|         assert p1 != p2
 | |
|         assert p1 != 'foo'
 | |
| 
 | |
|     def test_process__hash__(self):
 | |
|         s = {psutil.Process(), psutil.Process()}
 | |
|         assert len(s) == 1
 | |
| 
 | |
| 
 | |
| # ===================================================================
 | |
| # --- Misc, generic, corner cases
 | |
| # ===================================================================
 | |
| 
 | |
| 
 | |
| class TestMisc(PsutilTestCase):
 | |
|     def test__all__(self):
 | |
|         dir_psutil = dir(psutil)
 | |
|         for name in dir_psutil:
 | |
|             if name in {
 | |
|                 'debug',
 | |
|                 'tests',
 | |
|                 'test',
 | |
|                 'PermissionError',
 | |
|                 'ProcessLookupError',
 | |
|             }:
 | |
|                 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()
 | |
|                         ):
 | |
|                             raise pytest.fail(
 | |
|                                 f"{name!r} not in psutil.__all__"
 | |
|                             )
 | |
| 
 | |
|         # 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
 | |
|         # so we simply iterate over __all__.
 | |
|         for name in psutil.__all__:
 | |
|             assert name in dir_psutil
 | |
| 
 | |
|     def test_version(self):
 | |
|         assert (
 | |
|             '.'.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'
 | |
|         assert 'foo' not in p.as_dict()
 | |
| 
 | |
|     def test_serialization(self):
 | |
|         def check(ret):
 | |
|             json.loads(json.dumps(ret))
 | |
| 
 | |
|             a = pickle.dumps(ret)
 | |
|             b = pickle.loads(a)
 | |
|             assert ret == b
 | |
| 
 | |
|         # --- process APIs
 | |
| 
 | |
|         proc = psutil.Process()
 | |
|         check(psutil.Process().as_dict())
 | |
| 
 | |
|         ns = process_namespace(proc)
 | |
|         for fun, name in ns.iter(ns.getters, clear_cache=True):
 | |
|             with self.subTest(proc=str(proc), name=name):
 | |
|                 try:
 | |
|                     ret = fun()
 | |
|                 except psutil.Error:
 | |
|                     pass
 | |
|                 else:
 | |
|                     check(ret)
 | |
| 
 | |
|         # --- system APIs
 | |
| 
 | |
|         ns = system_namespace()
 | |
|         for fun, name in ns.iter(ns.getters):
 | |
|             if name in {"win_service_iter", "win_service_get"}:
 | |
|                 continue
 | |
|             with self.subTest(name=name):
 | |
|                 try:
 | |
|                     ret = fun()
 | |
|                 except psutil.AccessDenied:
 | |
|                     pass
 | |
|                 else:
 | |
|                     check(ret)
 | |
| 
 | |
|         # --- exception classes
 | |
| 
 | |
|         b = pickle.loads(
 | |
|             pickle.dumps(
 | |
|                 psutil.NoSuchProcess(pid=4567, name='name', msg='msg')
 | |
|             )
 | |
|         )
 | |
|         assert isinstance(b, psutil.NoSuchProcess)
 | |
|         assert b.pid == 4567
 | |
|         assert b.name == 'name'
 | |
|         assert b.msg == 'msg'
 | |
| 
 | |
|         b = pickle.loads(
 | |
|             pickle.dumps(
 | |
|                 psutil.ZombieProcess(pid=4567, name='name', ppid=42, msg='msg')
 | |
|             )
 | |
|         )
 | |
|         assert isinstance(b, psutil.ZombieProcess)
 | |
|         assert b.pid == 4567
 | |
|         assert b.ppid == 42
 | |
|         assert b.name == 'name'
 | |
|         assert b.msg == 'msg'
 | |
| 
 | |
|         b = pickle.loads(
 | |
|             pickle.dumps(psutil.AccessDenied(pid=123, name='name', msg='msg'))
 | |
|         )
 | |
|         assert isinstance(b, psutil.AccessDenied)
 | |
|         assert b.pid == 123
 | |
|         assert b.name == 'name'
 | |
|         assert b.msg == 'msg'
 | |
| 
 | |
|         b = pickle.loads(
 | |
|             pickle.dumps(
 | |
|                 psutil.TimeoutExpired(seconds=33, pid=4567, name='name')
 | |
|             )
 | |
|         )
 | |
|         assert isinstance(b, psutil.TimeoutExpired)
 | |
|         assert b.seconds == 33
 | |
|         assert b.pid == 4567
 | |
|         assert b.name == 'name'
 | |
| 
 | |
|     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, '_get_ident', side_effect=psutil.AccessDenied
 | |
|         ) as meth:
 | |
|             psutil.Process()
 | |
|             assert meth.called
 | |
| 
 | |
|         with mock.patch.object(
 | |
|             psutil.Process, '_get_ident', side_effect=psutil.ZombieProcess(1)
 | |
|         ) as meth:
 | |
|             psutil.Process()
 | |
|             assert meth.called
 | |
| 
 | |
|         with mock.patch.object(
 | |
|             psutil.Process, '_get_ident', side_effect=ValueError
 | |
|         ) as meth:
 | |
|             with pytest.raises(ValueError):
 | |
|                 psutil.Process()
 | |
|             assert meth.called
 | |
| 
 | |
|         with mock.patch.object(
 | |
|             psutil.Process, '_get_ident', side_effect=psutil.NoSuchProcess(1)
 | |
|         ) as meth:
 | |
|             with pytest.raises(psutil.NoSuchProcess):
 | |
|                 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 pytest.raises(ImportError) as cm:
 | |
|                 reload_module(psutil)
 | |
|             assert "version conflict" in str(cm.value).lower()
 | |
| 
 | |
| 
 | |
| # ===================================================================
 | |
| # --- psutil/_common.py utils
 | |
| # ===================================================================
 | |
| 
 | |
| 
 | |
| class TestMemoizeDecorator(PsutilTestCase):
 | |
|     def setUp(self):
 | |
|         self.calls = []
 | |
| 
 | |
|     tearDown = setUp
 | |
| 
 | |
|     def run_against(self, obj, expected_retval=None):
 | |
|         # no args
 | |
|         for _ in range(2):
 | |
|             ret = obj()
 | |
|             assert self.calls == [((), {})]
 | |
|             if expected_retval is not None:
 | |
|                 assert ret == expected_retval
 | |
|         # with args
 | |
|         for _ in range(2):
 | |
|             ret = obj(1)
 | |
|             assert self.calls == [((), {}), ((1,), {})]
 | |
|             if expected_retval is not None:
 | |
|                 assert ret == expected_retval
 | |
|         # with args + kwargs
 | |
|         for _ in range(2):
 | |
|             ret = obj(1, bar=2)
 | |
|             assert self.calls == [((), {}), ((1,), {}), ((1,), {'bar': 2})]
 | |
|             if expected_retval is not None:
 | |
|                 assert ret == expected_retval
 | |
|         # clear cache
 | |
|         assert len(self.calls) == 3
 | |
|         obj.cache_clear()
 | |
|         ret = obj()
 | |
|         if expected_retval is not None:
 | |
|             assert ret == expected_retval
 | |
|         assert len(self.calls) == 4
 | |
|         # docstring
 | |
|         assert obj.__doc__ == "My docstring."
 | |
| 
 | |
|     def test_function(self):
 | |
|         @memoize
 | |
|         def foo(*args, **kwargs):
 | |
|             """My docstring."""
 | |
|             baseclass.calls.append((args, kwargs))
 | |
|             return 22
 | |
| 
 | |
|         baseclass = self
 | |
|         self.run_against(foo, expected_retval=22)
 | |
| 
 | |
|     def test_class(self):
 | |
|         @memoize
 | |
|         class Foo:
 | |
|             """My docstring."""
 | |
| 
 | |
|             def __init__(self, *args, **kwargs):
 | |
|                 baseclass.calls.append((args, kwargs))
 | |
| 
 | |
|             def bar(self):
 | |
|                 return 22
 | |
| 
 | |
|         baseclass = self
 | |
|         self.run_against(Foo, expected_retval=None)
 | |
|         assert Foo().bar() == 22
 | |
| 
 | |
|     def test_class_singleton(self):
 | |
|         # @memoize can be used against classes to create singletons
 | |
|         @memoize
 | |
|         class Bar:
 | |
|             def __init__(self, *args, **kwargs):
 | |
|                 pass
 | |
| 
 | |
|         assert Bar() is Bar()
 | |
|         assert id(Bar()) == id(Bar())
 | |
|         assert id(Bar(1)) == id(Bar(1))
 | |
|         assert id(Bar(1, foo=3)) == id(Bar(1, foo=3))
 | |
|         assert id(Bar(1)) != id(Bar(2))
 | |
| 
 | |
|     def test_staticmethod(self):
 | |
|         class Foo:
 | |
|             @staticmethod
 | |
|             @memoize
 | |
|             def bar(*args, **kwargs):
 | |
|                 """My docstring."""
 | |
|                 baseclass.calls.append((args, kwargs))
 | |
|                 return 22
 | |
| 
 | |
|         baseclass = self
 | |
|         self.run_against(Foo().bar, expected_retval=22)
 | |
| 
 | |
|     def test_classmethod(self):
 | |
|         class Foo:
 | |
|             @classmethod
 | |
|             @memoize
 | |
|             def bar(cls, *args, **kwargs):
 | |
|                 """My docstring."""
 | |
|                 baseclass.calls.append((args, kwargs))
 | |
|                 return 22
 | |
| 
 | |
|         baseclass = self
 | |
|         self.run_against(Foo().bar, expected_retval=22)
 | |
| 
 | |
|     def test_original(self):
 | |
|         # This was the original test before I made it dynamic to test it
 | |
|         # against different types. Keeping it anyway.
 | |
|         @memoize
 | |
|         def foo(*args, **kwargs):
 | |
|             """Foo docstring."""
 | |
|             calls.append(None)
 | |
|             return (args, kwargs)
 | |
| 
 | |
|         calls = []
 | |
|         # no args
 | |
|         for _ in range(2):
 | |
|             ret = foo()
 | |
|             expected = ((), {})
 | |
|             assert ret == expected
 | |
|             assert len(calls) == 1
 | |
|         # with args
 | |
|         for _ in range(2):
 | |
|             ret = foo(1)
 | |
|             expected = ((1,), {})
 | |
|             assert ret == expected
 | |
|             assert len(calls) == 2
 | |
|         # with args + kwargs
 | |
|         for _ in range(2):
 | |
|             ret = foo(1, bar=2)
 | |
|             expected = ((1,), {'bar': 2})
 | |
|             assert ret == expected
 | |
|             assert len(calls) == 3
 | |
|         # clear cache
 | |
|         foo.cache_clear()
 | |
|         ret = foo()
 | |
|         expected = ((), {})
 | |
|         assert ret == expected
 | |
|         assert len(calls) == 4
 | |
|         # docstring
 | |
|         assert foo.__doc__ == "Foo docstring."
 | |
| 
 | |
| 
 | |
| class TestCommonModule(PsutilTestCase):
 | |
|     def test_memoize_when_activated(self):
 | |
|         class Foo:
 | |
|             @memoize_when_activated
 | |
|             def foo(self):
 | |
|                 calls.append(None)
 | |
| 
 | |
|         f = Foo()
 | |
|         calls = []
 | |
|         f.foo()
 | |
|         f.foo()
 | |
|         assert len(calls) == 2
 | |
| 
 | |
|         # activate
 | |
|         calls = []
 | |
|         f.foo.cache_activate(f)
 | |
|         f.foo()
 | |
|         f.foo()
 | |
|         assert len(calls) == 1
 | |
| 
 | |
|         # deactivate
 | |
|         calls = []
 | |
|         f.foo.cache_deactivate(f)
 | |
|         f.foo()
 | |
|         f.foo()
 | |
|         assert len(calls) == 2
 | |
| 
 | |
|     def test_parse_environ_block(self):
 | |
|         def k(s):
 | |
|             return s.upper() if WINDOWS else s
 | |
| 
 | |
|         assert parse_environ_block("a=1\0") == {k("a"): "1"}
 | |
|         assert parse_environ_block("a=1\0b=2\0\0") == {
 | |
|             k("a"): "1",
 | |
|             k("b"): "2",
 | |
|         }
 | |
|         assert parse_environ_block("a=1\0b=\0\0") == {k("a"): "1", k("b"): ""}
 | |
|         # ignore everything after \0\0
 | |
|         assert parse_environ_block("a=1\0b=2\0\0c=3\0") == {
 | |
|             k("a"): "1",
 | |
|             k("b"): "2",
 | |
|         }
 | |
|         # ignore everything that is not an assignment
 | |
|         assert parse_environ_block("xxx\0a=1\0") == {k("a"): "1"}
 | |
|         assert parse_environ_block("a=1\0=b=2\0") == {k("a"): "1"}
 | |
|         # do not fail if the block is incomplete
 | |
|         assert parse_environ_block("a=1\0b=2") == {k("a"): "1"}
 | |
| 
 | |
|     def test_supports_ipv6(self):
 | |
|         if supports_ipv6():
 | |
|             with mock.patch('psutil._common.socket') as s:
 | |
|                 s.has_ipv6 = False
 | |
|                 assert not supports_ipv6()
 | |
| 
 | |
|             with mock.patch(
 | |
|                 'psutil._common.socket.socket', side_effect=OSError
 | |
|             ) as s:
 | |
|                 assert not supports_ipv6()
 | |
|                 assert s.called
 | |
| 
 | |
|             with mock.patch(
 | |
|                 'psutil._common.socket.socket', side_effect=socket.gaierror
 | |
|             ) as s:
 | |
|                 assert not supports_ipv6()
 | |
|                 assert s.called
 | |
| 
 | |
|             with mock.patch(
 | |
|                 'psutil._common.socket.socket.bind',
 | |
|                 side_effect=socket.gaierror,
 | |
|             ) as s:
 | |
|                 assert not supports_ipv6()
 | |
|                 assert s.called
 | |
|         else:
 | |
|             with pytest.raises(OSError):
 | |
|                 sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
 | |
|                 try:
 | |
|                     sock.bind(("::1", 0))
 | |
|                 finally:
 | |
|                     sock.close()
 | |
| 
 | |
|     def test_isfile_strict(self):
 | |
|         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=PermissionError):
 | |
|             with pytest.raises(OSError):
 | |
|                 isfile_strict(this_file)
 | |
|         with mock.patch(
 | |
|             'psutil._common.os.stat', side_effect=FileNotFoundError
 | |
|         ):
 | |
|             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_debug(self):
 | |
|         with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True):
 | |
|             with contextlib.redirect_stderr(io.StringIO()) as f:
 | |
|                 debug("hello")
 | |
|                 sys.stderr.flush()
 | |
|         msg = f.getvalue()
 | |
|         assert msg.startswith("psutil-debug"), msg
 | |
|         assert "hello" in msg
 | |
|         assert __file__.replace('.pyc', '.py') in msg
 | |
| 
 | |
|         # supposed to use repr(exc)
 | |
|         with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True):
 | |
|             with contextlib.redirect_stderr(io.StringIO()) as f:
 | |
|                 debug(ValueError("this is an error"))
 | |
|         msg = f.getvalue()
 | |
|         assert "ignoring ValueError" in msg
 | |
|         assert "'this is an error'" in msg
 | |
| 
 | |
|         # supposed to use str(exc), because of extra info about file name
 | |
|         with mock.patch.object(psutil._common, "PSUTIL_DEBUG", True):
 | |
|             with contextlib.redirect_stderr(io.StringIO()) as f:
 | |
|                 exc = OSError(2, "no such file")
 | |
|                 exc.filename = "/foo"
 | |
|                 debug(exc)
 | |
|         msg = f.getvalue()
 | |
|         assert "no such file" in msg
 | |
|         assert "/foo" in msg
 | |
| 
 | |
|     def test_cat_bcat(self):
 | |
|         testfn = self.get_testfn()
 | |
|         with open(testfn, "w") as f:
 | |
|             f.write("foo")
 | |
|         assert cat(testfn) == "foo"
 | |
|         assert bcat(testfn) == b"foo"
 | |
|         with pytest.raises(FileNotFoundError):
 | |
|             cat(testfn + '-invalid')
 | |
|         with pytest.raises(FileNotFoundError):
 | |
|             bcat(testfn + '-invalid')
 | |
|         assert cat(testfn + '-invalid', fallback="bar") == "bar"
 | |
|         assert bcat(testfn + '-invalid', fallback="bar") == "bar"
 | |
| 
 | |
| 
 | |
| # ===================================================================
 | |
| # --- Tests for wrap_numbers() function.
 | |
| # ===================================================================
 | |
| 
 | |
| 
 | |
| nt = collections.namedtuple('foo', 'a b c')
 | |
| 
 | |
| 
 | |
| class TestWrapNumbers(PsutilTestCase):
 | |
|     def setUp(self):
 | |
|         wrap_numbers.cache_clear()
 | |
| 
 | |
|     tearDown = setUp
 | |
| 
 | |
|     def test_first_call(self):
 | |
|         input = {'disk1': nt(5, 5, 5)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
| 
 | |
|     def test_input_hasnt_changed(self):
 | |
|         input = {'disk1': nt(5, 5, 5)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
| 
 | |
|     def test_increase_but_no_wrap(self):
 | |
|         input = {'disk1': nt(5, 5, 5)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
|         input = {'disk1': nt(10, 15, 20)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
|         input = {'disk1': nt(20, 25, 30)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
|         input = {'disk1': nt(20, 25, 30)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
| 
 | |
|     def test_wrap(self):
 | |
|         # let's say 100 is the threshold
 | |
|         input = {'disk1': nt(100, 100, 100)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
|         # first wrap restarts from 10
 | |
|         input = {'disk1': nt(100, 100, 10)}
 | |
|         assert wrap_numbers(input, 'disk_io') == {'disk1': nt(100, 100, 110)}
 | |
|         # then it remains the same
 | |
|         input = {'disk1': nt(100, 100, 10)}
 | |
|         assert wrap_numbers(input, 'disk_io') == {'disk1': nt(100, 100, 110)}
 | |
|         # then it goes up
 | |
|         input = {'disk1': nt(100, 100, 90)}
 | |
|         assert wrap_numbers(input, 'disk_io') == {'disk1': nt(100, 100, 190)}
 | |
|         # then it wraps again
 | |
|         input = {'disk1': nt(100, 100, 20)}
 | |
|         assert wrap_numbers(input, 'disk_io') == {'disk1': nt(100, 100, 210)}
 | |
|         # and remains the same
 | |
|         input = {'disk1': nt(100, 100, 20)}
 | |
|         assert wrap_numbers(input, 'disk_io') == {'disk1': nt(100, 100, 210)}
 | |
|         # now wrap another num
 | |
|         input = {'disk1': nt(50, 100, 20)}
 | |
|         assert wrap_numbers(input, 'disk_io') == {'disk1': nt(150, 100, 210)}
 | |
|         # and again
 | |
|         input = {'disk1': nt(40, 100, 20)}
 | |
|         assert wrap_numbers(input, 'disk_io') == {'disk1': nt(190, 100, 210)}
 | |
|         # keep it the same
 | |
|         input = {'disk1': nt(40, 100, 20)}
 | |
|         assert 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)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
|         input = {'disk1': nt(5, 5, 5), 'disk2': nt(7, 7, 7)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
|         input = {'disk1': nt(8, 8, 8)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
| 
 | |
|     def test_changing_keys_w_wrap(self):
 | |
|         input = {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 100)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
|         # disk 2 wraps
 | |
|         input = {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 10)}
 | |
|         assert wrap_numbers(input, 'disk_io') == {
 | |
|             'disk1': nt(50, 50, 50),
 | |
|             'disk2': nt(100, 100, 110),
 | |
|         }
 | |
|         # disk 2 disappears
 | |
|         input = {'disk1': nt(50, 50, 50)}
 | |
|         assert 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)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
|         # remains the same
 | |
|         input = {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 100)}
 | |
|         assert wrap_numbers(input, 'disk_io') == input
 | |
|         # and then wraps again
 | |
|         input = {'disk1': nt(50, 50, 50), 'disk2': nt(100, 100, 10)}
 | |
|         assert 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),
 | |
|         }
 | |
|         assert wrap_numbers(d, 'disk_io') == d
 | |
|         assert 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')
 | |
|         assert 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()
 | |
|         assert cache[0] == {'disk_io': input}
 | |
|         assert cache[1] == {'disk_io': {}}
 | |
|         assert 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()
 | |
|         assert cache[0] == {'disk_io': input}
 | |
|         assert cache[1] == {
 | |
|             'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 0}
 | |
|         }
 | |
|         assert 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()
 | |
|         assert cache[0] == {'disk_io': input}
 | |
|         assert cache[1] == {
 | |
|             'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 100}
 | |
|         }
 | |
|         assert cache[2] == {'disk_io': {'disk1': {('disk1', 2)}}}
 | |
| 
 | |
|         def check_cache_info():
 | |
|             cache = wrap_numbers.cache_info()
 | |
|             assert cache[1] == {
 | |
|                 'disk_io': {
 | |
|                     ('disk1', 0): 0,
 | |
|                     ('disk1', 1): 0,
 | |
|                     ('disk1', 2): 100,
 | |
|                 }
 | |
|             }
 | |
|             assert cache[2] == {'disk_io': {'disk1': {('disk1', 2)}}}
 | |
| 
 | |
|         # then it remains the same
 | |
|         input = {'disk1': nt(100, 100, 10)}
 | |
|         wrap_numbers(input, 'disk_io')
 | |
|         cache = wrap_numbers.cache_info()
 | |
|         assert cache[0] == {'disk_io': input}
 | |
|         check_cache_info()
 | |
| 
 | |
|         # then it goes up
 | |
|         input = {'disk1': nt(100, 100, 90)}
 | |
|         wrap_numbers(input, 'disk_io')
 | |
|         cache = wrap_numbers.cache_info()
 | |
|         assert cache[0] == {'disk_io': input}
 | |
|         check_cache_info()
 | |
| 
 | |
|         # then it wraps again
 | |
|         input = {'disk1': nt(100, 100, 20)}
 | |
|         wrap_numbers(input, 'disk_io')
 | |
|         cache = wrap_numbers.cache_info()
 | |
|         assert cache[0] == {'disk_io': input}
 | |
|         assert cache[1] == {
 | |
|             'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 190}
 | |
|         }
 | |
|         assert cache[2] == {'disk_io': {'disk1': {('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()
 | |
|         assert cache[0] == {'disk_io': input}
 | |
|         assert cache[1] == {
 | |
|             'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 0}
 | |
|         }
 | |
|         assert 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')
 | |
|         assert wrap_numbers.cache_info() == ({}, {}, {})
 | |
|         wrap_numbers.cache_clear('disk_io')
 | |
|         wrap_numbers.cache_clear('?!?')
 | |
| 
 | |
|     @pytest.mark.skipif(not HAS_NET_IO_COUNTERS, reason="not supported")
 | |
|     def test_cache_clear_public_apis(self):
 | |
|         if not psutil.disk_io_counters() or not psutil.net_io_counters():
 | |
|             raise pytest.skip("no disks or NICs available")
 | |
|         psutil.disk_io_counters()
 | |
|         psutil.net_io_counters()
 | |
|         caches = wrap_numbers.cache_info()
 | |
|         for cache in caches:
 | |
|             assert 'psutil.disk_io_counters' in cache
 | |
|             assert 'psutil.net_io_counters' in cache
 | |
| 
 | |
|         psutil.disk_io_counters.cache_clear()
 | |
|         caches = wrap_numbers.cache_info()
 | |
|         for cache in caches:
 | |
|             assert 'psutil.net_io_counters' in cache
 | |
|             assert 'psutil.disk_io_counters' not in cache
 | |
| 
 | |
|         psutil.net_io_counters.cache_clear()
 | |
|         caches = wrap_numbers.cache_info()
 | |
|         assert caches == ({}, {}, {})
 |