Search code examples
pythonpython-3.xpython-2.7mockingmagicmock

MagicMock not called for signal handler in Python 3


I was in the middle of migrating some code and associated tests from python 2 to python 3 (specifically 2.7 to 3.7) and I came across this weird behavior that I'd like to understand:

try:
    from unittest import mock  # python 3
except ImportError:
    import mock  # python 2
import os
import signal


def do_signal(mock_type):
    def side_effect(signum, frame):
        print('Called for ' + mock_type.__name__)
    handler = mock_type(side_effect=side_effect)
    signal.signal(signal.SIGTERM, handler)
    os.kill(os.getpid(), signal.SIGTERM)
    print(handler.call_count)


do_signal(mock.Mock)
do_signal(mock.MagicMock)

Here I use a mock.Mock or mock.MagicMock as a signal handler, and I print out the number of times the mock was called after sending the signal to the current process.

In python 2.7 this prints out:

Called for Mock
1
Called for MagicMock
1

But for python3.7 it prints out:

Called for Mock
1
0

For python 3 the MagicMock doesn't seem to be called at all. Reversing the order of the do_signal calls doesn't change this.

What behavior differences between Mock and MagicMock could explain this, and why would the behavior change from python 2 to 3? I know that mock was added to the standard library in python 3 as unittest.mock, so I'm curious whether something changed there.


Solution

  • in python 3, enumerations were added for signals

    for example, you can call:

    signal.signal(signal.SIGINT, signal.SIG_IGN)
    

    and the first thing signal.signal attempts to do is convert the handle to an integer from an enum by calling int(...)

    since MagicMock supports all of the magic methods, it also supports __int__

    you can see this if you peek at the magic method calls:

        # ...
        try:
            print(handler.__int__.call_count)
        except AttributeError:
            print('__int__ not called')
    

    since the MagicMock has __int__ :

    >>> mock.MagicMock().__int__()
    1
    

    that reassigns the SIGTERM handler to signal 1 (SIGHUP on my platform)