2

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.

srm
  • 548
  • 1
  • 4
  • 19

1 Answers1

0

I've resolved this by changing the function and mocking.

srm
  • 548
  • 1
  • 4
  • 19