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.
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)