This is a story about the details of a C type leaking into Python.
As part of testing my Python wrapper for SysV IPC, I wrote tests for the time-related attributes of the IPC objects that change when something happens to the object. For instance, when someone sends a message to a queue, the queue’s last_send_time
attribute (msg_stime
in the C code) is updated to the current time.
I have a hard time imagining many programmers care about these attributes. Knowing the last time someone changed the uid
of a message queue, for instance, just doesn’t have many use cases. But they’re part of the SysV IPC API and so I want my package to expose them.
I wrote tests to ensure that each one changed when it was supposed to. The tests failed consistently although the code worked when I tested it “by hand” in an interactive shell. Here’s the relevant portion of a failing test:
def test_property_last_change_time(self): """exercise MessageQueue.last_change_time""" original_last_change_time = self.mq.last_change_time # This might seem like a no-op, but setting the UID to # any value triggers a call to msgctl(...IPC_STAT...) # which should set last_change_time. self.mq.uid = self.mq.uid # Ensure the time actually changed. self.assertNotEqual(self.mq.last_change_time, original_last_change_time)
The problem is obvious, right? No, it wasn’t obvious to me, either.
The problem is that in C, a message queue’s last change time (msg_ctime
) is of variable type time_t
which is typedef-ed as an integral type (int
or long
) on most (all?) systems. Because the test above executed in less than 1 second, the assertion always failed. Setting self.mq.uid
correctly caused an update to the last change time (msg_ctime
), it was just being updated to the same value that had been saved in the first line of the test.
My solution was to add a little sleeping, like so –
def test_property_last_change_time(self): """exercise MessageQueue.last_change_time""" original_last_change_time = self.mq.last_change_time time.sleep(1.1) # This might seem like a no-op, but setting the UID to # any value triggers a call to msgctl(...IPC_STAT...) # which should set last_change_time. self.mq.uid = self.mq.uid # Ensure the time actually changed. self.assertNotEqual(self.mq.last_change_time, original_last_change_time)
That ensured that the value stored in original_last_change_time
at the start of the test would differ from self.mq.last_change_time
by at least 1 at the end of the test.
Lessons learned: don’t forget about C types, especially when you’re wrapping a C API.