I have a method (below), in a module utils.py
that uses psutil.process_iter
to find and generate process info. dicts (psutil.Process.as_dict
) based on the name of a given process. If the process name doesn't exist, or process errors are encountered, nothing is generated. The module contains of course the import of psutil
.
def get_processes_by_name(process_name):
for proc in psutil.process_iter():
try:
pinfo = proc.as_dict()
if process_name == pinfo['name']:
yield pinfo
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
I am trying to write a number of unit tests for this method, for different scenarios, using unittest.mock
. The test cases are in a test module test_utils.py
. The following is an example.
def test__process_instances_by_name__one_running_proc_matches_given_proc_name__one_proc_instance_generated(self):
mock_proc1 = MagicMock()
mock_proc1.as_dict = MagicMock(return_value={'name': 'some_other_proc_name'})
mock_proc2 = MagicMock()
mock_proc2.as_dict = MagicMock(side_effect=psutil.AccessDenied(2))
mock_proc3 = MagicMock()
mock_proc3.as_dict = MagicMock(return_value={'name': 'test_proc_name', 'exe': 'test/proc/exec/path', 'pid': 3})
with patch('utils.psutil.process_iter', MagicMock()) as mock_process_iter:
mock_process_iter.iter.return_value = iter([mock_proc1, mock_proc2, mock_proc3])
received_process_instances = list(get_processes_by_name('test_proc_name'))
self.assertEqual(received_process_instances, [mock_proc3])
Note that I am not patching psutil.process_iter
directly, but its import in the target module utils.py
in which the target method is defined. I think is the correct approach.
In this test case here I create three mock processes: the first (mock_proc1
) generates a process error (psutil.AccessDenied
) when as_dict
is called, the second (mock_proc2
) has a different name to the one I'm looking for, but the last one (mock_proc3
) represents a valid process with the required name test_proc_name
. The idea is that when get_processes_by_name
is called with test_proc_name
the method should generate mock_proc3
only. As get_processes_by_name
is a generator (following psutil.process_iter
) I have followed the unittest
docs on how to mock generating methods.
Unfortunately, this test case is failing - the target method is not generating anything so the assertion fails (ipdb
output below).
ipdb>
> /path/to/tests/test_utils.py(352)test__get_processes_by_name__one_running_proc_matches_given_proc_name__one_proc_instance_generated()
351
--> 352 self.assertEqual(received_process_instances, [mock_proc3])
353
ipdb>
AssertionError: Lists differ: [] != [<MagicMock id='4660019536'>]
Second list contains 1 additional elements.
First extra element 0:
<MagicMock id='4660019536'>
I have tried to debug the target method using ipdb
(putting a breakpoint at the top of the target method), and I can see the following output.
> /path/to/utils.py(183)get_processes_by_name()
182 import ipdb; ipdb.set_trace()
--> 183 for proc in psutil.process_iter():
184 try:
ipdb>
ipdb> n
StopIteration
> /path/to/venv/lib/python3.7/site-packages/pluggy/callers.py(203)_multicall()
202 try:
--> 203 gen.send(outcome)
204 _raise_wrapfail(gen, "has second yield")
ipdb>
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
[... skipped 1 hidden frame]
I don't know what is going on here. Is it complaining about the multiple yields in psutil.process_iter
, or is there an error in the mocking. Any suggestions would be welcome.